diff --git a/README.md b/README.md index 6d79a78..8547d70 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # bellatui -Command line bella renderer with encrypted networking and text user interface. +Command line bella renderer with encrypted networking ,text user interface and file monitoring. ## Usage @@ -54,6 +54,7 @@ workdir/ ├── bella_engine_sdk/ ├── libzmq/ ├── cppzmq/ +├── efsw/ ├── belatui/ ( additional Windows package manager dependency ) @@ -95,9 +96,14 @@ cd build make -j4 cd ../.. git clone https://github.com/zeromq/cppzmq +git clone https://github.com/SpartanJ/efsw.git +mkdir -p efsw/build +cd efsw/build +/Applications/CMake.app/Contents/bin/cmake .. +cd ../.. git clone https://github.com/oomer/bellatui.git cd bellatui -make -j4 +make all -j4 ``` ## Linux @@ -151,7 +157,13 @@ git clone https://github.com/zeromq/cppzmq cd cppzmq mkdir build cd build -cmake .. +cmake .. +cd ../.. +git clone https://github.com/SpartanJ/efsw.git +mkdir -p efsw/build +cd efsw/build +cmake .. +make -j4 ``` ### compiling bellatui @@ -159,7 +171,7 @@ cmake .. cd ../.. git clone https://github.com/oomer/bellatui.git cd bellatui -make +make all -j4 ``` # Windows @@ -173,18 +185,17 @@ Get bella_engine_sdk git clone https://github.com/microsoft/vcpkg.git cd vcpkg vcpkg install zeromq[sodium]:x64-windows +cd .. +git clone https://github.com/SpartanJ/efsw.git +mkdir -p efsw/build +cd efsw/build +cmake .. git clone https://github.com/oomer/bellatui.git msbuild bellatui.vcxproj /p:Configuration=release /p:Platform=x64 /p:PlatformToolset=v143 ``` -Build directories expected to be relative -``` ---folder - --bella_engine_sdk - --bellatui -``` diff --git a/bellatui.cpp b/bellatui.cpp index ab781cf..e2d9b4f 100644 --- a/bellatui.cpp +++ b/bellatui.cpp @@ -37,21 +37,263 @@ #include // For waitpid #endif -#include "bella_sdk/bella_engine.h" -#include "dl_core/dl_fs.h" +#include // For file watching +#include // For file watching +#include // For file watching +#include +#include + + +#include "../bella_engine_sdk/src/bella_sdk/bella_engine.h" // For rendering +#include "../bella_engine_sdk/src/dl_core/dl_fs.h" // For rendering using namespace dl; using namespace dl::bella_sdk; +/// A class that manages a queue of files to render with both FIFO order and fast lookups +class RenderQueue { +public: + // Default constructor + RenderQueue() = default; + + // Move constructor + RenderQueue(RenderQueue&& other) noexcept { + std::lock_guard lock(other.mutex); + pathVector = std::move(other.pathVector); + pathMap = std::move(other.pathMap); + } + + // Move assignment operator + RenderQueue& operator=(RenderQueue&& other) noexcept { + if (this != &other) { + std::lock_guard lock1(mutex); + std::lock_guard lock2(other.mutex); + pathVector = std::move(other.pathVector); + pathMap = std::move(other.pathMap); + } + return *this; + } + + // Delete copy operations since mutexes can't be copied + RenderQueue(const RenderQueue&) = delete; + RenderQueue& operator=(const RenderQueue&) = delete; + + // Add a file to the queue if it's not already there + bool push(const dl::String& path) { + std::lock_guard lock(mutex); + if (pathMap.find(path) == pathMap.end()) { + pathVector.push_back(path); + pathMap[path] = true; + return true; + } + return false; + } + + // Get the next file to render (FIFO order) + bool pop(dl::String& outPath) { + std::lock_guard lock(mutex); + if (!pathVector.empty()) { + outPath = pathVector.front(); + pathVector.erase(pathVector.begin()); + pathMap.erase(outPath); + return true; + } + return false; + } + + // Remove a specific file by name + bool remove(const dl::String& path) { + std::lock_guard lock(mutex); + if (pathMap.find(path) != pathMap.end()) { + // Remove from vector using erase-remove idiom + pathVector.erase( + std::remove(pathVector.begin(), pathVector.end(), path), + pathVector.end() + ); + // Remove from map + pathMap.erase(path); + return true; + } + return false; + } + + // Check if a file exists in the queue + bool contains(const dl::String& path) const { + std::lock_guard lock(mutex); + return pathMap.find(path) != pathMap.end(); + } + + // Get the number of files in the queue + size_t size() const { + std::lock_guard lock(mutex); + return pathVector.size(); + } + + // Check if the queue is empty + bool empty() const { + std::lock_guard lock(mutex); + return pathVector.empty(); + } + + // Clear all files from the queue + void clear() { + std::lock_guard lock(mutex); + pathVector.clear(); + pathMap.clear(); + } + +private: + std::vector pathVector; // Maintains FIFO order + std::map pathMap; // Enables fast lookups + mutable std::mutex mutex; // Thread safety +}; + +std::atomic active_render(false); +RenderQueue renderQueue; // Replace the old vector and map with our new class +std::mutex renderQueueMutex; // Add mutex for thread safety +std::vector renderDelete; // This is the efsw queue for when we delete a file +std::mutex renderDeleteMutex; // Add mutex for thread safety + +dl::String currentRender; +std::mutex currentRenderMutex; // Add mutex for thread safety + +// Queues for incoming files from the efsw watcher +RenderQueue incomingDeleteQueue; +RenderQueue incomingRenderQueue; +std::mutex incomingDeleteQueueMutex; // Add mutex for thread safety +std::mutex incomingRenderQueueMutex; // Add mutex for thread safety + + +/// Processes a file action +class UpdateListener : public efsw::FileWatchListener { + public: + UpdateListener() : should_stop_(false) {} + + void stop() { + should_stop_ = true; + } + + 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 { + if (should_stop_) return; // Early exit if we're stopping + + std::string actionName = getActionName( action ); + /*std::cout << "Watch ID " << watchid << " DIR (" + << dir + ") FILE (" + + ( oldFilename.empty() ? "" : "from file " + oldFilename + " to " ) + + filename + ") has event " + << actionName << std::endl;*/ + if (actionName == "Delete") { + if (active_render || !incomingRenderQueue.empty()) { + dl::String belPath = (dir + filename).c_str(); + if (belPath.endsWith(".bsz")) { + { + std::lock_guard lock(incomingDeleteQueueMutex); + if (!incomingDeleteQueue.contains(belPath)) { + incomingDeleteQueue.push(belPath); + std::cout << "\n==" << "STOP RENDER: " << belPath.buf() << "\n==" << std::endl; + } + } + } + } + } + if (actionName == "Add" || actionName == "Modified") { + dl::String belPath = (dir + filename).c_str(); + if (should_stop_) return; // Check again before starting render + if (belPath.endsWith(".bsz")) { + { + std::lock_guard lock(incomingRenderQueueMutex); + if (!incomingRenderQueue.contains(belPath)) { + incomingRenderQueue.push(belPath); + std::cout << "\n==" << "RENDER QUEUED: " << belPath.buf() << "\n==" << std::endl; + } + } + } + } + } + private: + std::atomic should_stop_; // ctrl-c was not working, so we use this to stop the thread +}; + // Global state variables std::string initializeGlobalLicense(); // Function to return license text std::string initializeGlobalThirdPartyLicences(); // Function to return third-party licenses std::atomic connection_state (false); // Tracks if client/server are connected std::atomic abort_state (false); // Used to signal program termination std::atomic server (false); // Indicates if running in server mode +UpdateListener* global_ul = nullptr; // Global pointer to UpdateListener // Function declarations std::string get_pubkey_from_srv(std::string server_address, uint16_t publickey_port); // Gets server's public key for encryption + +bool STOP = false; + +void sigend( int ) { + std::cout << std::endl << "Bye bye" << std::endl; + STOP = true; + if (global_ul) { // Use the global pointer + global_ul->stop(); + } + // Give a short time for cleanup + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + exit(0); // Force exit after cleanup +} + +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; +} + + +static int s_logCtx = 0; +static void log(void* /*ctx*/, LogType type, const char* msg) +{ + switch (type) + { + case LogType_Info: + DL_PRINT("[INFO] %s\n", msg); + break; + case LogType_Warning: + DL_PRINT("[WARN] %s\n", msg); + break; + case LogType_Error: + DL_PRINT("[ERROR] %s\n", msg); + break; + case LogType_Custom: + DL_PRINT("%s\n", msg); + break; + } +} + // Main client communication thread void client_thread( std::string server_pkey, std::string client_pkey, @@ -59,12 +301,6 @@ void client_thread( std::string server_pkey, std::string server_address, uint16_t command_port); -// Main server thread that handles rendering and client requests -void server_thread( std::string server_skey, - uint16_t command_port, - bool test_render, - Engine engine); - // Utility function to open files with system default program void openFileWithDefaultProgram(const std::string& filePath); @@ -89,14 +325,15 @@ public: // Called when a rendering pass starts void onStarted(String pass) override { + std::cout << "Started pass " << pass.buf() << std::endl; 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()); - } + //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(String pass, Progress progress) override @@ -106,6 +343,11 @@ public: 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(String pass, String msg) override { @@ -116,6 +358,7 @@ public: void onStopped(String pass) override { logInfo("Stopped %s", pass.buf()); + active_render = false; } // Returns the current progress as a string @@ -143,6 +386,16 @@ private: } }; +// Main server thread that handles client requests +void server_thread( std::string server_skey, + uint16_t command_port, + bool test_render, + Engine& engine, + MyEngineObserver& engineObserver); + +void render_thread( Engine& engine, + MyEngineObserver& engineObserver); + /* * Heartbeat Monitoring System * @@ -245,6 +498,41 @@ void heartbeat_thread( std::string server_pkey, ctx.close(); } +void file_watcher_thread(const std::string& watch_path = "") { + bool commonTest = true; + bool useGeneric = false; + + global_ul = new UpdateListener(); + efsw::FileWatcher fileWatcher(useGeneric); + + fileWatcher.followSymlinks(false); + fileWatcher.allowOutOfScopeLinks(false); + + if (!watch_path.empty() && dl::fs::exists(watch_path.data())) { + commonTest = false; + if (fileWatcher.addWatch(watch_path, global_ul, true) > 0) { + fileWatcher.watch(); + std::cout << "Watching directory: " << watch_path << std::endl; + } else { + std::cout << "Error trying to watch directory: " << watch_path << std::endl; + std::cout << efsw::Errors::Log::getLastErrorLog().c_str() << std::endl; + return; + } + } else if (commonTest) { + std::string CurPath(efsw::System::getProcessPath()); + std::cout << "CurPath: " << CurPath.c_str() << std::endl; + fileWatcher.watch(); + handleWatchID(fileWatcher.addWatch(CurPath + "test", global_ul, true)); + } + + while(STOP == false) { + efsw::System::sleep(500); + } + + delete global_ul; + global_ul = nullptr; +} + /* * Main Program Entry Point * @@ -274,6 +562,18 @@ int DL_main(Args& args) uint16_t publickey_port = 5799; bool test_render = false; + Engine engine; + engine.scene().loadDefs(); + MyEngineObserver engineObserver; + engine.subscribe(&engineObserver); + + // Very early on, we will subscribe to the global bella logging callback, and ask to flush + // any messages that may have accumulated prior to this point. + // + subscribeLog(&s_logCtx, log); + flushStartupMessages(); + + // Register command-line arguments args.add("sa", "serverAddress", "", "Bella render server ip address"); args.add("cp", "commandPort", "", "tcp port for zmq server socket for commands"); @@ -283,6 +583,8 @@ int DL_main(Args& args) args.add("tr", "testRender", "", "force res to 100x100"); args.add("tp", "thirdparty", "", "prints third party licenses"); args.add("li", "licenseinfo", "", "prints license info"); + args.add("ef", "efsw", "", "mode efsw"); + args.add("wd", "watchdir", "", "mode file warch"); // Handle special command-line options if (args.versionReqested()) @@ -296,6 +598,39 @@ int DL_main(Args& args) printf("%s", args.help("SDK Test", fs::exePath(), bellaSdkVersion().toString()).buf()); return 0; } + std::string path="."; + + if (args.have("--watchdir")) { + path = args.value("--watchdir").buf(); + } + + + //EFSW mode alwys on + // Create the file watcher thread + std::thread watcher_thread(file_watcher_thread, path); + // Don't wait for the thread to finish here, let it run in background + watcher_thread.detach(); + + /*if (args.have("--efswxxxxx")) + { + std::cout << "EFSW mode" << std::endl; + signal( SIGABRT, sigend ); + signal( SIGINT, sigend ); + signal( SIGTERM, sigend ); + + //std::cout << "Press ^C to exit demo" << std::endl; + + std::string path; + if (args.have("--watchdir")) { + path = args.value("--watchdir").buf(); + } + + // Create the file watcher thread + std::thread watcher_thread(file_watcher_thread, path); + + // Don't wait for the thread to finish here, let it run in background + watcher_thread.detach(); + }*/ // Show license information if requested if (args.have("--licenseinfo")) @@ -364,9 +699,6 @@ int DL_main(Args& args) } - Engine engine; - engine.scene().loadDefs(); - // Generate brand new keypair on launch // [TODO] Add client side public key fingerprinting for added security if(server.load()) { @@ -378,7 +710,8 @@ int DL_main(Args& args) std::cout << "\ncurve keypair gen failed."; exit(EXIT_FAILURE); } - std::thread server_t(server_thread, server_skey, command_port, test_render, engine); + std::thread server_t(server_thread, server_skey, command_port, test_render, std::ref(engine), std::ref(engineObserver)); + std::thread render_t(render_thread, std::ref(engine), std::ref(engineObserver)); ///std::thread heartbeat_t(heartbeat_thread, server_skey, server.load(), 5555); std::thread heartbeat_t(heartbeat_thread, //function "", //NA Public server key @@ -397,7 +730,7 @@ int DL_main(Args& args) while(true) { // inner loop if (connection_state.load()==false) { - std::cout << "Client connectiono dead" << std::endl; + std::cout << "Client connection dead" << std::endl; break; // Go back to awaiting client } std::this_thread::sleep_for(std::chrono::milliseconds(10)); @@ -595,6 +928,16 @@ void client_thread( std::string server_pkey, exit(0); // RENDER } else if(command == "render") { + std::string compoundArg; + if(num_args > 1) { + for (size_t i = 1; i < args.size(); ++i) { + compoundArg += args[i]; + if (i < args.size() - 1) { + compoundArg += " "; // Add spaces between arguments + } + } + std::cout << compoundArg << std::endl; + } //>>>ZOUT command_sock.send(zmq::message_t("render"), zmq::send_flags::none); //ZIN<<< @@ -694,9 +1037,10 @@ void client_thread( std::string server_pkey, void server_thread( std::string server_skey, uint16_t command_port, bool test_render, - Engine engine) { - MyEngineObserver engineObserver; - engine.subscribe(&engineObserver); + Engine& engine, + MyEngineObserver& engineObserver) { + //MyEngineObserver engineObserver; + //engine.subscribe(&engineObserver); zmq::context_t ctx; zmq::socket_t command_sock(ctx, zmq::socket_type::rep); @@ -733,12 +1077,9 @@ void server_thread( std::string server_skey, command_sock.send(zmq::message_t("RDY"), zmq::send_flags::none); connection_state = false; //<< // RENDER - } else if (client_command == "render") { + } else if (client_command == "xxxxrender") { std::cout << "start render" << std::endl; - if(test_render) { - engine.scene().camera()["resolution"]= Vec2 {100, 100}; - } - engine.start(); + //>>>ZOUT command_sock.send(zmq::message_t("render started...type stat to get progress"), zmq::send_flags::none); } else if (client_command == "stop") { @@ -834,6 +1175,9 @@ void server_thread( std::string server_skey, //>>ZOUT command_sock.send(zmq::message_t("ACK"), zmq::send_flags::none); } + + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); } @@ -854,6 +1198,85 @@ void server_thread( std::string server_skey, ctx.close(); } +void render_thread( Engine& engine, + MyEngineObserver& engineObserver) { + // Create persistent instances outside the loop + RenderQueue renderThreadQueue; + RenderQueue renderThreadDeleteQueue; + + while (true) { + // Append items from incoming queues to our persistent queues + { + std::lock_guard lock(incomingRenderQueueMutex); + // Process each item in the incoming queue and add it to our persistent queue + dl::String path; + while (incomingRenderQueue.pop(path)) { + renderThreadQueue.push(path); + } + incomingRenderQueue.clear(); + } + + { + std::lock_guard lock(incomingDeleteQueueMutex); + // Process each item in the incoming queue and add it to our persistent queue + dl::String path; + while (incomingDeleteQueue.pop(path)) { + renderThreadDeleteQueue.push(path); + } + incomingDeleteQueue.clear(); + } + + // Process the files without holding the mutex + bool expected = false; + + // This is an atomic operation that does two things at once: + // 1. Checks if active_render equals expected (false) + // 2. If they are equal, sets active_render to true + // + // The operation is atomic, meaning no other thread can interfere + // between the check and the set. This prevents two threads from + // both thinking they can start rendering at the same time. + // + // Returns true if the exchange was successful (we got the render slot) + // Returns false if active_render was already true (someone else is rendering) + dl::String belPath; + if (active_render.compare_exchange_strong(expected, true)) { + // We successfully got the render slot - no one else is rendering + if (renderThreadQueue.pop(belPath)) { + std::cout << "\n==" << "RENDERING: " << belPath.buf() << "\n==" << std::endl; + engine.loadScene(belPath); + engine.scene().camera()["resolution"]= Vec2 {100, 100}; + engine.start(); + { + std::lock_guard lock(currentRenderMutex); + currentRender = belPath; + } + } else { + active_render = false; // Release the render slot + } + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } else { // someone else is rendering + std::cout << "Waiting for render slot" << std::endl; + + //std::cout << "Render Queue size: " << renderThreadQueue.size() << std::endl; + //std::cout << "Delete Queue size: " << renderThreadDeleteQueue.size() << std::endl; + while (renderThreadDeleteQueue.pop(belPath)) { // pop all the deletes + std::cout << "renderThreadDeleteQueue contains " << belPath.buf() << " " << renderThreadDeleteQueue.contains(belPath) << std::endl; + if (belPath == currentRender) { + std::cout << "/n==/nStopping render" << belPath.buf() << std::endl; + engine.stop(); + active_render = false; + } else if (renderThreadQueue.contains(belPath)) { // dequeue deletes + renderThreadQueue.remove(belPath); + } + } + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } +} + void openFileWithDefaultProgram(const std::string& filePath) { #ifdef _WIN32 diff --git a/makefile b/makefile index 5d0139a..028a57a 100644 --- a/makefile +++ b/makefile @@ -1,143 +1,137 @@ -SDKNAME =bella_engine_sdk -OUTNAME =bellatui -UNAME =$(shell uname) +BELLA_SDK_NAME = bella_engine_sdk +EXECUTABLE_NAME = bellatui +PLATFORM = $(shell uname) +BUILD_TYPE ?= release# Default to release build if not specified -ifeq ($(UNAME), Darwin) +# Common paths +BELLA_SDK_PATH = ../bella_engine_sdk +LIBEFSW_PATH = ../efsw +LIBZMQ_PATH = ../libzmq +CPPZMQ_PATH = ../cppzmq - SDKBASE = ../bella_engine_sdk +OBJ_DIR = obj/$(PLATFORM)/$(BUILD_TYPE) +BIN_DIR = bin/$(PLATFORM)/$(BUILD_TYPE) +OUTPUT_FILE = $(BIN_DIR)/$(EXECUTABLE_NAME) - SDKFNAME = lib$(SDKNAME).dylib - ZMQNAME = libzmq.5.dylib - INCLUDEDIRS = -I$(SDKBASE)/src - INCLUDEDIRS2 = -I../cppzmq - INCLUDEDIRS3 = -I../libzmq/include - LIBDIR = $(SDKBASE)/lib - ZMQDIR = ../libzmq/build/lib - LIBDIRS2 = -L../libzmq/build/lib - LIBDIRS = -L$(LIBDIR) - OBJDIR = obj/$(UNAME) - BINDIR = bin/$(UNAME) - OUTPUT = $(BINDIR)/$(OUTNAME) +# Platform-specific configuration +ifeq ($(PLATFORM), Darwin) + # macOS configuration + SDK_LIB_EXT = dylib +# LZFSE_LIB_NAME = liblzfse.$(SDK_LIB_EXT) +# PLIST_LIB_NAME = libplist-2.0.4.$(SDK_LIB_EXT) + ZMQ_LIB_NAME = libzmq.5.$(SDK_LIB_EXT) + EFSW_LIB_NAME = libefsw.$(SDK_LIB_EXT) + MACOS_SDK_PATH = /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk - ISYSROOT = /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk + # Compiler settings + CC = clang + CXX = clang++ - CC = clang - CXX = clang++ + # Architecture flags + ARCH_FLAGS = -arch arm64 -mmacosx-version-min=11.0 -isysroot $(MACOS_SDK_PATH) - CCFLAGS = -arch x86_64\ - -arch arm64\ - -mmacosx-version-min=11.0\ - -isysroot $(ISYSROOT)\ - -fvisibility=hidden\ - -O3\ - $(INCLUDEDIRS)\ - $(INCLUDEDIRS2)\ - $(INCLUDEDIRS3)\ - $(LIBDIRS2) + # Linking flags - Use multiple rpath entries to look in executable directory + LINKER_FLAGS = $(ARCH_FLAGS) -framework Cocoa -framework IOKit -fvisibility=hidden -O5 \ + -rpath @executable_path \ + -rpath . - CFLAGS = $(CCFLAGS)\ - -std=c11 + #-rpath @loader_path \ + #-Xlinker -rpath -Xlinker @executable_path - CXXFLAGS = $(CCFLAGS)\ - -std=c++11 - - CPPDEFINES = -DNDEBUG=1\ - -DDL_USE_SHARED - - LIBS = -l$(SDKNAME)\ - -lm\ - -lzmq\ - -ldl - - LINKFLAGS = -mmacosx-version-min=11.0\ - -isysroot $(ISYSROOT)\ - -framework Cocoa\ - -framework IOKit\ - -framework CoreVideo\ - -framework CoreFoundation\ - -framework Accelerate\ - -fvisibility=hidden\ - -O5\ - -rpath @executable_path\ - -weak_library $(LIBDIR)/libvulkan.dylib else + # Linux configuration + SDK_LIB_EXT = so +# LZFSE_LIB_NAME = liblzfse.$(SDK_LIB_EXT) +# PLIST_LIB_NAME = libplist.$(SDK_LIB_EXT) + ZMQ_LIB_NAME = libzmq.$(SDK_LIB_EXT) + EFSW_LIB_NAME = libefsw.$(SDK_LIB_EXT) - SDKBASE = ../bella_engine_sdk + # Compiler settings + CC = gcc + CXX = g++ - SDKFNAME = lib$(SDKNAME).so - ZMQNAME = libzmq.so.5 - SODNAME = libsodium.so.23 - INCLUDEDIRS = -I$(SDKBASE)/src - INCLUDEDIRS2 = -I../cppzmq - INCLUDEDIRS3 = -I../libzmq/include - LIBDIR = $(SDKBASE)/lib - ZMQDIR = ../libzmq/build/lib - SODDIR = /usr/lib/x86_64-linux-gnu/ - LIBDIRS = -L$(LIBDIR) - LIBDIRS2 = -L../libzmq/build/lib - OBJDIR = obj/$(UNAME) - BINDIR = bin/$(UNAME) - OUTPUT = $(BINDIR)/$(OUTNAME) + # Architecture flags + ARCH_FLAGS = -m64 -D_FILE_OFFSET_BITS=64 - CC = gcc - CXX = g++ + # Linking flags + LINKER_FLAGS = $(ARCH_FLAGS) -fvisibility=hidden -O3 -Wl,-rpath,'$$ORIGIN' -Wl,-rpath,'$$ORIGIN/lib' -weak_library $(LIBDIR)/libvulkan.dylib - CCFLAGS = -m64\ - -Wall\ - -fvisibility=hidden\ - -D_FILE_OFFSET_BITS=64\ - -O3\ - $(INCLUDEDIRS)\ - $(INCLUDEDIRS2)\ - $(INCLUDEDIRS3)\ - $(LIBDIRS2) - - CFLAGS = $(CCFLAGS)\ - -std=c11 - - CXXFLAGS = $(CCFLAGS)\ - -std=c++11 - - CPPDEFINES = -DNDEBUG=1\ - -DDL_USE_SHARED - - LIBS = -l$(SDKNAME)\ - -lm\ - -ldl\ - -lrt\ - -lpthread\ - -lX11\ - -lGL\ - -lzmq\ - -lvulkan - - LINKFLAGS = -m64\ - -fvisibility=hidden\ - -O3\ - -Wl,-rpath,'$$ORIGIN'\ - -Wl,-rpath,'$$ORIGIN/lib' + # Platform-specific libraries + #PLIST_LIB = -lplist endif -OBJS = bellatui.o -OBJ = $(patsubst %,$(OBJDIR)/%,$(OBJS)) -$(OBJDIR)/%.o: %.cpp - @mkdir -p $(@D) - $(CXX) -c -o $@ $< $(CXXFLAGS) $(CPPDEFINES) +# Common include and library paths +INCLUDE_PATHS = -I$(BELLA_SDK_PATH)/src -I$(LIBEFSW_PATH)/include -I$(LIBEFSW_PATH)/src -I$(LIBZMQ_PATH)/include -I$(CPPZMQ_PATH) +SDK_LIB_PATH = $(BELLA_SDK_PATH)/lib +SDK_LIB_FILE = lib$(BELLA_SDK_NAME).$(SDK_LIB_EXT) +EFSW_LIB_PATH = $(LIBEFSW_PATH)/build +#EFSW_LIB_FILE = lib$(EFSW_LIB_NAME) +ZMQ_LIB_PATH = $(LIBZMQ_PATH)/build/lib +# Library flags +LIB_PATHS = -L$(SDK_LIB_PATH) -L$(EFSW_LIB_PATH) -L$(ZMQ_LIB_PATH) +LIBRARIES = -l$(BELLA_SDK_NAME) -lm -ldl -lefsw -lzmq -$(OUTPUT): $(OBJ) - @mkdir -p $(@D) - $(CXX) -o $@ $^ $(LINKFLAGS) $(LIBDIRS) $(LIBDIRS2) $(LIBS) - @cp $(LIBDIR)/$(SDKFNAME) $(BINDIR)/$(SDKFNAME) - @cp $(ZMQDIR)/$(ZMQNAME) $(BINDIR)/$(ZMQNAME) - chmod 755 $(BINDIR)/$(ZMQNAME) -ifeq ($(UNAME), Linux) - @cp $(SODDIR)/$(SODNAME) $(BINDIR)/$(SODNAME) +# Build type specific flags +ifeq ($(BUILD_TYPE), debug) + CPP_DEFINES = -D_DEBUG -DDL_USE_SHARED + COMMON_FLAGS = $(ARCH_FLAGS) -fvisibility=hidden -g -O0 $(INCLUDE_PATHS) +else + CPP_DEFINES = -DNDEBUG=1 -DDL_USE_SHARED + COMMON_FLAGS = $(ARCH_FLAGS) -fvisibility=hidden -O3 $(INCLUDE_PATHS) endif -.PHONY: clean +# Language-specific flags +C_FLAGS = $(COMMON_FLAGS) -std=c17 +CXX_FLAGS = $(COMMON_FLAGS) -std=c++17 -Wno-deprecated-declarations + +# Objects +OBJECTS = $(EXECUTABLE_NAME).o +OBJECT_FILES = $(patsubst %,$(OBJ_DIR)/%,$(OBJECTS)) + +# Build rules +$(OBJ_DIR)/$(EXECUTABLE_NAME).o: $(EXECUTABLE_NAME).cpp + @mkdir -p $(@D) + $(CXX) -c -o $@ $< $(CXX_FLAGS) $(CPP_DEFINES) + +$(OUTPUT_FILE): $(OBJECT_FILES) + @mkdir -p $(@D) + $(CXX) -o $@ $(OBJECT_FILES) $(LINKER_FLAGS) $(LIB_PATHS) $(LIBRARIES) + @echo "Copying libraries to $(BIN_DIR)..." + @cp $(SDK_LIB_PATH)/$(SDK_LIB_FILE) $(BIN_DIR)/$(SDK_LIB_FILE) +# @cp $(LZFSE_BUILD_DIR)/$(LZFSE_LIB_NAME) $(BIN_DIR)/$(LZFSE_LIB_NAME) +# @cp $(PLIST_LIB_DIR)/$(PLIST_LIB_NAME) $(BIN_DIR)/$(PLIST_LIB_NAME) + @cp $(EFSW_LIB_PATH)/$(EFSW_LIB_NAME) $(BIN_DIR)/$(EFSW_LIB_NAME) + @cp $(ZMQ_LIB_PATH)/$(ZMQ_LIB_NAME) $(BIN_DIR)/$(ZMQ_LIB_NAME) + @echo "Build complete: $(OUTPUT_FILE)" + +# Add default target +all: $(OUTPUT_FILE) + +.PHONY: clean cleanall all clean: - rm -f $(OBJDIR)/*.o - rm -f $(OUTPUT) - rm -f $(BINDIR)/$(SDKFNAME) + rm -f $(OBJ_DIR)/$(EXECUTABLE_NAME).o + rm -f $(OUTPUT_FILE) + rm -f $(BIN_DIR)/$(SDK_LIB_FILE) + rm -f $(BIN_DIR)/*.dylib + rmdir $(OBJ_DIR) 2>/dev/null || true + rmdir $(BIN_DIR) 2>/dev/null || true + +cleanall: + rm -f obj/*/release/*.o + rm -f obj/*/debug/*.o + rm -f bin/*/release/$(EXECUTABLE_NAME) + rm -f bin/*/debug/$(EXECUTABLE_NAME) + rm -f bin/*/release/$(SDK_LIB_FILE) + rm -f bin/*/debug/$(SDK_LIB_FILE) + rm -f bin/*/release/*.dylib + rm -f bin/*/debug/*.dylib + rmdir obj/*/release 2>/dev/null || true + rmdir obj/*/debug 2>/dev/null || true + rmdir bin/*/release 2>/dev/null || true + rmdir bin/*/debug 2>/dev/null || true + rmdir obj/* 2>/dev/null || true + rmdir bin/* 2>/dev/null || true + rmdir obj 2>/dev/null || true + rmdir bin 2>/dev/null || true \ No newline at end of file