joomer-efsw-bsz-monitoring/joomer-efsw-bsz-monitoring.cpp
2026-03-14 17:24:21 -04:00

453 lines
14 KiB
C++

#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <efsw/FileSystem.hpp>
#include <efsw/System.hpp>
#include <efsw/efsw.hpp>
#include <iostream>
#include <signal.h>
#include <cstring>
#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 <thread> // For std::this_thread (C++ standard library)
#include <chrono> // For timing operations (C++ standard library)
#include <cstdio> // For freopen
#include <regex>
std::string directory_path = ".";
bool STOP = false;
dl::bella_sdk::Engine engine;
std::string fileToRender = "";
bool pendingRender = false;
int width = 800;
int height = 600;
//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<std::string*> 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'
dl::bella_sdk::Node pathNode = engine.scene().createNode("outputImagePath");
pathNode["dir"] = dl::String(directory_path.c_str());
//pathNode["dir"] = dl::String(".");
pathNode["name"] = dl::String("render_result");
pathNode["ext"] = dl::String(".png");
// Get the global settings node instead of the beautyPass node
dl::bella_sdk::Node settings = engine.scene().settings();
dl::bella_sdk::Node cam = settings["camera"].asNode();
auto inputs = settings.inputs(); // This gets all valid attribute names
for (const auto& input : inputs) {
std::cout << "Valid attribute: " << input.name() << std::endl;
}
if (cam) {
auto inputs = cam.inputs();
for (const auto& input : inputs) {
// This will print every valid attribute name for YOUR camera
std::cout << "Camera Attribute: " << input.name() << std::endl;
}
if (cam) {
std::cout << ">>> Set resolution to " << width <<"x" << height << " on camera : " << cam.name() << std::endl;
float fWidth = static_cast<float>(width);
float fHeight = static_cast<float>(height);
cam["resolution"] = dl::Vec2{ fWidth, fHeight };
}
}
else {
std::cout << ">>> ERROR: No camera found in settings!" << std::endl;
}
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. Saved in current directory" << 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 ) {
#include "dl_core/dl_main.inl"
#include "dl_core/dl_args.h"
int DL_main(dl::Args& args) {
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;
args.add("wd", "watchdir", "", "mode file watch");
args.add("w", "width", "800", "render width");
args.add("h", "height", "600", "render height");
/*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;
}
}
}*/
//std::cout << "args: " << args. << std::endl;
if (args.have("--watchdir")) {
path = args.value("--watchdir").buf();
std::cout << "Watching path: " << path << std::endl;
if (efsw::FileSystem::isDirectory(path)) {
commonTest = false;
useGeneric = true; // Force generic for testing
}
}
if (args.have("--width")) {
width = std::stoi(args.value("--width").buf());
}
if (args.have("--height")) {
height = std::stoi(args.value("--height").buf());
}
UpdateListener* ul = new UpdateListener();
/// create the file watcher object
efsw::FileWatcher fileWatcher( useGeneric );
fileWatcher.followSymlinks( false );
fileWatcher.allowOutOfScopeLinks( false );
//std::cout << "commonTest: " << commonTest << std::endl;
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 , ul, true ) );
} else {
if ( fileWatcher.addWatch( path, ul, true ) > 0 ) {
fileWatcher.watch();
/// add a watch to the system
handleWatchID(fileWatcher.addWatch(path, ul, true));
std::cout << "Watching directory: " << path.c_str() << std::endl;
directory_path = path;
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;
}