diff --git a/README.md b/README.md index d55380a..a2e1198 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ joomer-efsw-file-monitoring # Build ``` learndir/ -└── joomer-efsw-file-monitoring/ +└── joomer-efsw-bsz-file-monitoring/ └── efsw ``` @@ -30,14 +30,14 @@ cd efsw/build cmake .. make -j4 cd ../.. -git clone https://git.indoodle.com/jason/joomer-efsw-file-monitoring.git -cd joomer-efsw-file-monitoring +git clone https://git.indoodle.com/jason/joomer-bsz-efsw-file-monitoring.git +cd joomer-bsz-efsw-file-monitoring git switch ProposedCleanup wget https://raw.githubusercontent.com/nothings/stb/master/stb_image.h wget https://raw.githubusercontent.com/nothings/stb/master/stb_image_write.h make all -j4 mkdir bin/Linux/release/test -bin/Linux/release/joomer-efsw-file-monitoring +bin/Linux/release/joomer-efsw-bsz-file-monitoring ``` ## Windows @@ -57,12 +57,12 @@ cd build cmake .. msbuild efsw.sln /p:Configuration=Release cd .. -git clone https://git.indoodle.com/jason/joomer-efsw-file-monitoring.git -cd joomer-efsw-file-monitoring +git clone https://git.indoodle.com/jason/joomer-efsw-bsz-monitoring.git +cd joomer-bsz-efsw-monitoring curl -LO https://raw.githubusercontent.com/nothings/stb/master/stb_image.h curl -LO https://raw.githubusercontent.com/nothings/stb/master/stb_image_write.h -msbuild joomer-efsw-file-monitoring.vcxproj /p:Configuration=release +msbuild joomer-efsw-bsz-monitoring.vcxproj /p:Configuration=release mkdir -p x64\release\test -x64\release\joomer-efsw-file-monitoring.exe +x64\release\joomer-efsw-bsz-monitoring.exe ``` diff --git a/joomer-efsw-bsz-monitoring.cpp b/joomer-efsw-bsz-monitoring.cpp new file mode 100644 index 0000000..813572d --- /dev/null +++ b/joomer-efsw-bsz-monitoring.cpp @@ -0,0 +1,397 @@ +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_WRITE_IMPLEMENTATION + +#include +#include +#include +#include +#include +#include +#include "stb_image.h" +#include "stb_image_write.h" + +#include "bella_sdk/bella_engine.h" // Core rendering engine +#include "bella_sdk/bella_scene.h" // Scene management +#include "dl_core/dl_logging.h" // Logging utilities +#include // For std::this_thread (C++ standard library) +#include // For timing operations (C++ standard library) +#include // For freopen +#include + + +std::string directory_path = "."; +bool STOP = false; +dl::bella_sdk::Engine engine; +std::string fileToRender = ""; +bool pendingRender = false; + +//void flip_image_horizontally(unsigned char* data, int width, int height, int channels); +void flip_image_vertically(unsigned char* data, int width, int height, int channels); + + +void sigend( int ) { + std::cout << std::endl << "close file monitoring" << std::endl; + STOP = true; +} + + +/* + * MyEngineObserver Class + * This class receives callbacks from the Bella rendering engine to track rendering progress. + * It implements the EngineObserver interface and provides methods to: + * - Handle render start/stop events + * - Track rendering progress + * - Handle error conditions + * - Store and retrieve the current progress state + */ +struct MyEngineObserver : public dl::bella_sdk::EngineObserver +{ +public: + // Called when a rendering pass starts + void onStarted(dl::String pass) override + { + //std::cout << "Started pass " << pass.buf() << std::endl; + // dl::logInfo("Started pass %s", pass.buf()); + } + + // Called to update the current status of rendering + //void onStatus(String pass, String status) override + //{ + // logInfo("%s [%s]", status.buf(), pass.buf()); + //} + + // Called to update rendering progress (percentage, time remaining, etc) + void onProgress(dl::String pass, dl::bella_sdk::Progress progress) override + { + //std::cout << progress.toString().buf() << std::endl; + setString(new std::string(progress.toString().buf())); + //dl::logInfo("%s [%s]", progress.toString().buf(), pass.buf()); + } + + //void onImage(String pass, Image image) override + //{ + // logInfo("We got an image %d x %d.", (int)image.width(), (int)image.height()); + //} + + // Called when an error occurs during rendering + void onError(dl::String pass, dl::String msg) override + { + //dl::logError("%s [%s]", msg.buf(), pass.buf()); + } + + // Called when a rendering pass completes + void onStopped(dl::String pass) override + { + // dl::logInfo("Stopped %s", pass.buf()); + std::atomic_bool active_render = false; + } + + // Returns the current progress as a string + std::string getProgress() const { + std::string* currentProgress = progressPtr.load(); + if (currentProgress) { + return *currentProgress; + } + else { + return ""; + } + } + + // Cleanup resources in destructor + ~MyEngineObserver() { + setString(nullptr); + } +private: + // Thread-safe pointer to current progress string + std::atomic progressPtr{ nullptr }; + + // Helper function to safely update the progress string + void setString(std::string* newStatus) { + std::string* oldStatus = progressPtr.exchange(newStatus); + delete oldStatus; // Clean up old string if it exists + } +}; + +MyEngineObserver engineObserver; + +//void render_thread(dl::bella_sdk::Engine& engine, MyEngineObserver& engineObserver, std::string selected_name) { +// // Construct the full path once +// dl::String fullPath = dl::String(directory_path.c_str()) + "/" + dl::String(selected_name.c_str()); +// +// // Load and then Read using the SAME full path +// engine.loadScene(fullPath); +// if (!engine.scene().read(fullPath)) { +// std::cout << "Failed to read scene: " << selected_name << std::endl; +// return; +// } +// +// //dl::String belPath = dl::String(directory_path.c_str()) + "\\"; +// //engine.loadScene(belPath + dl::String(selected_name.c_str())); +// +// //if (!engine.scene().read(dl::String(selected_name.c_str()))) return; +// +// dl::bella_sdk::Node bPass = engine.scene().beautyPass(); +// +// // Setup the outputImagePath node as we did in the synchronous version +// dl::bella_sdk::Node pathNode = engine.scene().createNode("outputImagePath"); +// pathNode["dir"] = dl::String(directory_path.c_str()); +// pathNode["name"] = "render_result"; +// pathNode["ext"] = ".png"; +// // In render_thread +// pathNode["dir"] = dl::String("../renders/"); +// bPass["overridePath"] = pathNode; +// bPass["timeLimit"] = 30.0f; +// +// engine.start(); +// +// // While rendering, sleep and then tell the UI to refresh +// while (engine.rendering()) { +// std::this_thread::sleep_for(std::chrono::milliseconds(500)); +// // This is the key to seeing progress while it's "asynchronous" +// //screen.PostEvent(ftxui::Event::Custom); +// //std::cout << "rendering..."; +// } +// std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // Finalize save +//} + +void do_sync_render(std::string fileToRender) +{ + // This removes any trailing slashes from directory_path to prevent the \/ issue + if (!directory_path.empty() && (directory_path.back() == '/' || directory_path.back() == '\\')) { + directory_path.pop_back(); + } + // 1. Force the path to look in the 'test' folder specifically + std::string fullPathStr = directory_path + "/" + fileToRender; + dl::String fullPath(fullPathStr.c_str()); + + std::cout << ">>> ATTEMPTING TO READ: " << fullPathStr << std::endl; + + engine.loadScene(fullPath); + if (!engine.scene().read(fullPath)) { + std::cout << ">>> ERROR: Bella could not read scene file!" << std::endl; + return; + } + + // 2. Setup the output path using a relative path that goes OUTSIDE 'test' + // This assumes your 'renders' folder is in x64/release/renders + dl::bella_sdk::Node pathNode = engine.scene().createNode("outputImagePath"); + pathNode["dir"] = dl::String("./renders/"); + pathNode["name"] = dl::String("render_result"); + pathNode["ext"] = dl::String(".png"); + + dl::bella_sdk::Node bPass = engine.scene().beautyPass(); + bPass["overridePath"] = pathNode; + + std::cout << ">>> STARTING BELLA ENGINE..." << std::endl; + engine.start(); + + // 3. Monitor progress + while (engine.rendering()) { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + std::cout << "rendering..." << std::endl; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // Give Windows time to finish the file write + std::cout << ">>> RENDER FINISHED. Check x64/release/renders/" << std::endl; +} + +void render_thread(dl::bella_sdk::Engine& engine, MyEngineObserver& engineObserver, std::string selected_name) { + // This removes any trailing slashes from directory_path to prevent the \/ issue + if (!directory_path.empty() && (directory_path.back() == '/' || directory_path.back() == '\\')) { + directory_path.pop_back(); + } + // 1. Force the path to look in the 'test' folder specifically + std::string fullPathStr = directory_path + "/" + selected_name; + dl::String fullPath(fullPathStr.c_str()); + + std::cout << ">>> ATTEMPTING TO READ: " << fullPathStr << std::endl; + + engine.loadScene(fullPath); + if (!engine.scene().read(fullPath)) { + std::cout << ">>> ERROR: Bella could not read scene file!" << std::endl; + return; + } + + // 2. Setup the output path using a relative path that goes OUTSIDE 'test' + // This assumes your 'renders' folder is in x64/release/renders + dl::bella_sdk::Node pathNode = engine.scene().createNode("outputImagePath"); + pathNode["dir"] = dl::String("../renders/"); + pathNode["name"] = dl::String("render_result"); + pathNode["ext"] = dl::String(".png"); + + dl::bella_sdk::Node bPass = engine.scene().beautyPass(); + bPass["overridePath"] = pathNode; + + std::cout << ">>> STARTING BELLA ENGINE..." << std::endl; + engine.start(); + + // 3. Monitor progress + while (engine.rendering()) { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + std::cout << "rendering..." << std::endl; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // Give Windows time to finish the file write + std::cout << ">>> RENDER FINISHED. Check x64/release/renders/" << std::endl; +} + + +/// Processes a file action +class UpdateListener : public efsw::FileWatchListener { + public: + UpdateListener() {} + + std::string getActionName( efsw::Action action ) { + switch ( action ) { + case efsw::Actions::Add: + + return "Add"; + case efsw::Actions::Modified: + return "Modified"; + case efsw::Actions::Delete: + return "Delete"; + case efsw::Actions::Moved: + return "Moved"; + default: + return "Bad Action"; + } + } + + void handleFileAction( efsw::WatchID watchid, const std::string& dir, + const std::string& filename, efsw::Action action, + std::string oldFilename = "" ) override { + std::cout << "Watch ID " << watchid << " DIR (" + << dir + ") FILE (" + + ( oldFilename.empty() ? "" : "from file " + oldFilename + " to " ) + + filename + ") has event " + << getActionName( action ) << std::endl; + + if (action == efsw::Actions::Add || action == efsw::Actions::Modified) { + directory_path = dir; + std::string fullPath = dir + filename; + int width, height, channels; + if (filename.substr(filename.length() - 3) == "bsz" ) { + std::cout << "Converting " << filename << " to a PNG" << std::endl; + // 2. Start the thread and DETACH it + // Note: We pass 'screen' as a reference so render_thread can call PostEvent + fileToRender = filename; + pendingRender = true; + //std::thread(render_thread, std::ref(engine), std::ref(engineObserver), filename).detach(); + } + } + } +}; + +efsw::WatchID handleWatchID( efsw::WatchID watchid ) { + switch ( watchid ) { + case efsw::Errors::FileNotFound: + case efsw::Errors::FileRepeated: + case efsw::Errors::FileOutOfScope: + case efsw::Errors::FileRemote: + case efsw::Errors::WatcherFailed: + case efsw::Errors::Unspecified: { + std::cout << efsw::Errors::Log::getLastErrorLog().c_str() << std::endl; + break; + } + default: { + std::cout << "Added WatchID: " << watchid << std::endl; + } + } + + return watchid; +} + +void flip_image_vertically(unsigned char* data, int width, int height, int channels) { + // ... (your vertical flip implementation from before) + int row_stride = width * channels; + unsigned char* temp_row = new unsigned char [row_stride]; + for (int y = 0; y < height / 2; ++y) { + unsigned char* top_row = data + y * row_stride; + unsigned char* bottom_row = data + (height - 1 - y) * row_stride; + std::memcpy(temp_row, top_row, row_stride); + std::memcpy(top_row, bottom_row, row_stride); + std::memcpy(bottom_row, temp_row, row_stride); + } + delete[] temp_row; +} + +int main( int argc, char** argv ) { + signal( SIGABRT, sigend ); + signal( SIGINT, sigend ); + signal( SIGTERM, sigend ); + // 1. Initialize engine and observer locally within the logic block + + engine.subscribe(&engineObserver); + + std::cout << "Press ^C to exit demo" << std::endl; + + bool commonTest = true; + bool useGeneric = false; + std::string path; + + if ( argc >= 2 ) { + path = std::string( argv[1] ); + + if ( efsw::FileSystem::isDirectory( path ) ) { + commonTest = false; + } + + if ( argc >= 3 ) { + if ( std::string( argv[2] ) == "true" ) { + useGeneric = true; + } + } + } + + UpdateListener* ul = new UpdateListener(); + + /// create the file watcher object + efsw::FileWatcher fileWatcher( useGeneric ); + + fileWatcher.followSymlinks( false ); + fileWatcher.allowOutOfScopeLinks( false ); + + if ( commonTest ) { + std::string CurPath( efsw::System::getProcessPath() ); + + std::cout << "CurPath: " << CurPath.c_str() << std::endl; + + /// starts watching + fileWatcher.watch(); + + /// add a watch to the system + handleWatchID( fileWatcher.addWatch( CurPath + "test", ul, true ) ); + + } else { + if ( fileWatcher.addWatch( path, ul, true ) > 0 ) { + fileWatcher.watch(); + + std::cout << "Watching directory: " << path.c_str() << std::endl; + + if ( useGeneric ) { + std::cout << "Using generic backend watcher" << std::endl; + } + } else { + std::cout << "Error trying to watch directory: " << path.c_str() << std::endl; + std::cout << efsw::Errors::Log::getLastErrorLog().c_str() << std::endl; + } + } + + while ( !STOP ) { + if (pendingRender) { + // We are now in the main thread. + // 1. Give the OS a moment to release file locks + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + // 2. Call render logic (not in a thread) + // Note: You'll need to move render_thread logic into a regular function + do_sync_render(fileToRender); + + pendingRender = false; + } + efsw::System::sleep( 100 ); + } + + return 0; +} + diff --git a/joomer-efsw-bsz-monitoring.vcxproj b/joomer-efsw-bsz-monitoring.vcxproj new file mode 100644 index 0000000..d8e0fe3 --- /dev/null +++ b/joomer-efsw-bsz-monitoring.vcxproj @@ -0,0 +1,120 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + joomer-efsw-bsz-monitoring + $(DefaultItemExcludes);$(ProjectDir)obj\** + 10.0 + {7AEA0690-36B7-4596-9DEE-C3AB3C11D282} + + + Release + x64 + $(DefaultPlatformToolset) + + + + Application + false + Unicode + + + Application + false + Unicode + $(DefaultPlatformToolset) + + + + + + + + + + + + + + + + + MultiThreadedDLL + false + DEBUG;_CONSOLE;DL_USE_SHARED;%(PreprocessorDefinitions) + Disabled + Disabled + ..\efsw\include;..\efsw\src;..\bella_engine_sdk\src; + + + Console + true + lib;..\efsw\build\$(Configuration)\$(Platform) + efsw-static.lib;%(AdditionalDependencies) + + + + + MultiThreadedDLL + true + true + false + NDEBUG;_CONSOLE;DL_USE_SHARED;%(PreprocessorDefinitions) + ..\efsw\include;..\efsw\src;..\bella_engine_sdk\src; + + + Console + true + true + true + lib;..\efsw\build\Release;..\bella_engine_sdk\lib; + efsw-static.lib;bella_engine_sdk.lib;%(AdditionalDependencies) + + + + echo Copying Bella SDK DLLs... + copy "$(ProjectDir)..\bella_engine_sdk\lib\bella_engine_sdk.dll" "$(TargetDir)" + copy "$(ProjectDir)..\bella_engine_sdk\lib\dl_core.dll" "$(TargetDir)" + copy "$(ProjectDir)..\bella_engine_sdk\lib\dl_oidn_core.dll" "$(TargetDir)" + copy "$(ProjectDir)..\bella_engine_sdk\lib\dl_usd_ms.dll" "$(TargetDir)" + echo DLL copy finished. + + + + + + TurnOffAllWarnings + false + _ALLOW_COMPILER_AND_STL_VERSION_MISMATCH;%(PreprocessorDefinitions) + NoIPO + + + false + + + + + + + + +