1
0
forked from oomer/bellatui

first working EFSW file monitoring, defaults to ., tested on macos

This commit is contained in:
Harvey Fong 2025-04-03 18:57:51 -06:00
parent c2087251e2
commit 7f478c7d6b
3 changed files with 585 additions and 157 deletions

View File

@ -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
@ -152,6 +158,12 @@ cd cppzmq
mkdir build
cd build
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
```

View File

@ -37,21 +37,263 @@
#include <sys/wait.h> // For waitpid
#endif
#include "bella_sdk/bella_engine.h"
#include "dl_core/dl_fs.h"
#include <efsw/FileSystem.hpp> // For file watching
#include <efsw/System.hpp> // For file watching
#include <efsw/efsw.hpp> // For file watching
#include <iostream>
#include <signal.h>
#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<std::mutex> 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<std::mutex> lock1(mutex);
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> lock(mutex);
return pathMap.find(path) != pathMap.end();
}
// Get the number of files in the queue
size_t size() const {
std::lock_guard<std::mutex> lock(mutex);
return pathVector.size();
}
// Check if the queue is empty
bool empty() const {
std::lock_guard<std::mutex> lock(mutex);
return pathVector.empty();
}
// Clear all files from the queue
void clear() {
std::lock_guard<std::mutex> lock(mutex);
pathVector.clear();
pathMap.clear();
}
private:
std::vector<dl::String> pathVector; // Maintains FIFO order
std::map<dl::String, bool> pathMap; // Enables fast lookups
mutable std::mutex mutex; // Thread safety
};
std::atomic<bool> 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<dl::String> 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<std::mutex> 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<std::mutex> lock(incomingRenderQueueMutex);
if (!incomingRenderQueue.contains(belPath)) {
incomingRenderQueue.push(belPath);
std::cout << "\n==" << "RENDER QUEUED: " << belPath.buf() << "\n==" << std::endl;
}
}
}
}
}
private:
std::atomic<bool> 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<bool> connection_state (false); // Tracks if client/server are connected
std::atomic<bool> abort_state (false); // Used to signal program termination
std::atomic<bool> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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

230
makefile
View File

@ -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)
ISYSROOT = /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
# 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
# Compiler settings
CC = clang
CXX = clang++
CCFLAGS = -arch x86_64\
-arch arm64\
-mmacosx-version-min=11.0\
-isysroot $(ISYSROOT)\
-fvisibility=hidden\
-O3\
$(INCLUDEDIRS)\
$(INCLUDEDIRS2)\
$(INCLUDEDIRS3)\
$(LIBDIRS2)
# Architecture flags
ARCH_FLAGS = -arch arm64 -mmacosx-version-min=11.0 -isysroot $(MACOS_SDK_PATH)
CFLAGS = $(CCFLAGS)\
-std=c11
# 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 .
CXXFLAGS = $(CCFLAGS)\
-std=c++11
#-rpath @loader_path \
#-Xlinker -rpath -Xlinker @executable_path
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
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)
# Compiler settings
CC = gcc
CXX = g++
CCFLAGS = -m64\
-Wall\
-fvisibility=hidden\
-D_FILE_OFFSET_BITS=64\
-O3\
$(INCLUDEDIRS)\
$(INCLUDEDIRS2)\
$(INCLUDEDIRS3)\
$(LIBDIRS2)
# Architecture flags
ARCH_FLAGS = -m64 -D_FILE_OFFSET_BITS=64
CFLAGS = $(CCFLAGS)\
-std=c11
# Linking flags
LINKER_FLAGS = $(ARCH_FLAGS) -fvisibility=hidden -O3 -Wl,-rpath,'$$ORIGIN' -Wl,-rpath,'$$ORIGIN/lib' -weak_library $(LIBDIR)/libvulkan.dylib
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