Compare commits

..

9 Commits

Author SHA1 Message Date
00ef227128 work on ServerModule 2025-04-16 01:26:56 -05:00
f94cb10412 ServerEvents 2025-04-15 00:35:31 -05:00
ba8d31335e build 2025-04-14 09:22:36 -05:00
80d15e7f48 naming 2025-04-13 19:51:28 -05:00
aa5d859f4a test build 2025-04-13 15:28:55 -05:00
ad2cec2360 add NetworkingModule 2025-04-13 14:50:53 -05:00
afed109c74 start TestNotCurses 2025-04-13 14:21:26 -05:00
640c19c439 add TestNotCurses 2025-04-13 13:37:32 -05:00
372e46168f add close feature 2025-04-12 20:26:12 -05:00
18 changed files with 580 additions and 43 deletions

View File

@@ -27,6 +27,37 @@
}; };
TestNotCurses = pkgs.stdenvNoCC.mkDerivation {
name = "TestNotCurses";
src = ./.;
nativeBuildInputs = with pkgs; [
clang
];
buildInputs = with pkgs; [
notcurses
];
buildPhase = ''
clang++ \
modules/examples/TestNotCurses/src/*.cpp \
-fpic -shared \
-I src -I include \
-Wall \
-lnotcurses \
-DTESTNOTCURSES_DYNAMIC \
-o $name
'';
installPhase = ''
mkdir -p $out/bin
cp $name $out/bin
'';
};
Print = pkgs.stdenvNoCC.mkDerivation { Print = pkgs.stdenvNoCC.mkDerivation {
name = "Print"; name = "Print";

View File

@@ -25,6 +25,71 @@
Modules = { Modules = {
examples = import ./ExampleModules.nix { inherit inputs; inherit pkgs; }; examples = import ./ExampleModules.nix { inherit inputs; inherit pkgs; };
ServerModule = pkgs.stdenvNoCC.mkDerivation {
name = "ServerModule";
src = ./.;
nativeBuildInputs = with pkgs; [
clang
];
buildInputs = with pkgs; [
gamenetworkingsockets
];
buildPhase = ''
clang++ \
modules/ServerModule/src/*.cpp \
-fpic -shared \
-I src -I include \
-I ${pkgs.gamenetworkingsockets}/include/GameNetworkingSockets \
-lGameNetworkingSockets \
-DSERVERMODULE_DYNAMIC \
-Wall \
-o $name
'';
installPhase = ''
mkdir -p $out/bin
cp $name $out/bin
'';
};
ClientModule = pkgs.stdenvNoCC.mkDerivation {
name = "ClientModule";
src = ./.;
nativeBuildInputs = with pkgs; [
clang
];
buildInputs = with pkgs; [
gamenetworkingsockets
];
buildPhase = ''
clang++ \
modules/ClientModule/src/*.cpp \
-fpic -shared \
-I src -I include \
-I ${pkgs.gamenetworkingsockets}/include/GameNetworkingSockets \
-lGameNetworkingSockets \
-DCLIENTMODULE_DYNAMIC \
-Wall \
-o $name
'';
installPhase = ''
mkdir -p $out/bin
cp $name $out/bin
'';
};
WindowModule = pkgs.stdenvNoCC.mkDerivation { WindowModule = pkgs.stdenvNoCC.mkDerivation {
name = "WindowModule"; name = "WindowModule";
@@ -88,6 +153,7 @@
-DRENDERER_OPENGL \ -DRENDERER_OPENGL \
-DWINDOW_GLFW \ -DWINDOW_GLFW \
-DIMGUIMODULE_DYNAMIC \ -DIMGUIMODULE_DYNAMIC \
-DCUSTOMFONT=${pkgs.fira-code}/share/fonts/truetype/FiraCode-VF.ttf \
-fpic -shared \ -fpic -shared \
-I src -I include -I $imgui -I . \ -I src -I include -I $imgui -I . \
-lGL -lglfw -lGLEW \ -lGL -lglfw -lGLEW \

View File

@@ -4,6 +4,8 @@
#include "utils/Module/Module.h" #include "utils/Module/Module.h"
#include "utils/App/App.h" #include "utils/App/App.h"
#include "utils/Events/Event.h"
#include "entryPoint.h" #include "entryPoint.h"
#endif #endif

View File

@@ -3,6 +3,7 @@
#include <iostream> #include <iostream>
#include <cstring> #include <cstring>
#include <cassert>
#include <list> #include <list>
#include <array> #include <array>
#include <vector> #include <vector>
@@ -10,6 +11,7 @@
#include <future> #include <future>
#include <unordered_map> #include <unordered_map>
#include <map>
#include <functional> #include <functional>
#include <optional> #include <optional>
#include <chrono> #include <chrono>
@@ -17,4 +19,7 @@
#include <dlfcn.h> #include <dlfcn.h>
#define STRINGIZE(x) #x
#define STRINGIZE_VALUE_OF(x) STRINGIZE(x)
#endif #endif

View File

@@ -3,6 +3,7 @@
#include "pch.hpp" #include "pch.hpp"
#include "utils/Module/Module.h" #include "utils/Module/Module.h"
#include "utils/Events/Event.h"
namespace Archimedes { namespace Archimedes {
@@ -53,22 +54,59 @@ namespace Archimedes {
void end() { done = true; } void end() { done = true; }
bool isDone() const { return done; }
void emitEvent(Event* e) { events.push_back(e); }
unsigned int getEventType(std::string event) { return eventTypes[event]; }
void addEventType(std::string type) {
//only add each type once
if(eventTypes.find(type) == eventTypes.end())
eventTypes[type] = nextEventType++;
}
void removeEventType(std::string type) {
//only erase registered types
auto it = eventTypes.find(type);
if(it != eventTypes.end())
eventTypes.erase(it);
}
private: private:
std::list<std::string>::iterator roInsert; std::list<std::string>::iterator roInsert;
inline static App* instance = nullptr; inline static App* instance = nullptr;
unsigned int nextEventType = 0;
protected: protected:
bool done = false; bool done = false;
std::unordered_map<std::string, Module*> modules; std::unordered_map<std::string, Module*> modules;
std::unordered_map<std::string, unsigned int> eventTypes;
std::list<Event*> events;
std::list<std::string> runOrder; std::list<std::string> runOrder;
std::list<std::string> toClose; std::list<std::string> toClose;
std::list<std::variant<std::string, Module*>> toOpen; std::list<std::variant<std::string, Module*>> toOpen;
void handleEvents() {
while(!events.empty()) {
for(auto it = runOrder.rbegin(); it != runOrder.rend(); it++) {
if(modules[*it]->onEvent(*events.front())) {
Event* e = events.front();
events.pop_front();
delete e;
break;
}
}
}
}
virtual Module* dynamicLoad(std::string lib) { virtual Module* dynamicLoad(std::string lib) {
void* h = dlopen(lib.c_str(), RTLD_NOW); void* h = dlopen(lib.c_str(), RTLD_NOW);
@@ -160,18 +198,12 @@ namespace Archimedes {
if(modules.find(name) == modules.end()) if(modules.find(name) == modules.end())
return; return;
/*
//unload modules that depend on the one we are unloading
for(std::string s : runOrder) {
if(modules[s]->deps.find(name) != modules[s]->deps.end()) {
unload(s);
}
}
*/
Module* m = modules[name]; Module* m = modules[name];
void* h = m->getHandle(); void* h = m->getHandle();
modules[name] = nullptr; modules[name] = nullptr;
modules.erase(name);
runOrder.remove(name); runOrder.remove(name);

View File

@@ -9,20 +9,9 @@ namespace Archimedes {
public: public:
enum class Type : unsigned int { virtual ~Event() {}
None = 0,
WindowEvent = 1 << 0,
KeyEvent = 1 << 1,
MouseMoveEvent = 1 << 2,
MouseScrollEvent = 1 << 3,
MouseButtonEvent = 1 << 4,
PressedEvent = 1 << 5,
ReleasedEvent = 1 << 6,
WindowCloseEvent = 1 << 7,
WindowResizeEvent = 1 << 8,
};
unsigned int type; virtual operator std::string() const = 0;
}; };
} }

View File

@@ -0,0 +1,21 @@
#include "ClientModule.h"
ClientModule::ClientModule(Archimedes::App* a, void* h) : Archimedes::Module(a, h) {
name = "ClientModule";
}
ClientModule::~ClientModule() {
GameNetworkingSockets_Kill();
}
void ClientModule::onLoad() {
SteamDatagramErrMsg errMsg;
if ( !GameNetworkingSockets_Init( nullptr, errMsg ) ) {
//FatalError( "GameNetworkingSockets_Init failed. %s", errMsg );
std::cerr << "GameNetworkingSockets_Init() Failed: " << errMsg << std::endl;
}
//g_logTimeZero = SteamNetworkingUtils()->GetLocalTimestamp();
//SteamNetworkingUtils()->SetDebugOutputFunction( k_ESteamNetworkingSocketsDebugOutputType_Msg, DebugOutput );
}

View File

@@ -0,0 +1,21 @@
#include "Archimedes.h"
#include <steam/steamnetworkingsockets.h>
#include <steam/isteamnetworkingutils.h>
class ClientModule : public Archimedes::Module {
public:
ClientModule(Archimedes::App*, void*);
~ClientModule();
void onLoad();
void run();
private:
};
#ifdef CLIENTMODULE_DYNAMIC
#define MODULE_TYPE ClientModule
#include "endModule.h"
#endif

View File

@@ -7,6 +7,8 @@
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
#include "pch.hpp"
ImguiModule::ImguiModule(Archimedes::App* a, void* h = nullptr) : Archimedes::Module(a, h) { ImguiModule::ImguiModule(Archimedes::App* a, void* h = nullptr) : Archimedes::Module(a, h) {
name = "ImguiModule"; name = "ImguiModule";
@@ -42,6 +44,9 @@ void ImguiModule::onLoad() {
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking
//io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows //io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows
#ifdef CUSTOMFONT
io.Fonts->AddFontFromFileTTF(STRINGIZE_VALUE_OF(CUSTOMFONT), 13.0f);
#endif
// Setup Dear ImGui style // Setup Dear ImGui style
ImGui::StyleColorsDark(); ImGui::StyleColorsDark();
//ImGui::StyleColorsLight(); //ImGui::StyleColorsLight();

View File

@@ -29,10 +29,11 @@ void MainGUI::onLoad() {
void MainGUI::run() { void MainGUI::run() {
if(open) {
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
static std::string s; static std::string s;
ImGui::Begin("MainGUI Module"); ImGui::Begin("MainGUI Module", &open);
ImGui::Text("Active Modules:"); ImGui::Text("Active Modules:");
for(auto m : app->getModuleNames()) for(auto m : app->getModuleNames())
@@ -51,4 +52,8 @@ void MainGUI::run() {
ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
ImGui::End(); ImGui::End();
} else {
app->end();
}
} }

View File

@@ -10,6 +10,8 @@ class MainGUI : public Archimedes::Module {
void onLoad(); void onLoad();
void run(); void run();
private:
bool open = true;
}; };
#ifdef MAINGUI_DYNAMIC #ifdef MAINGUI_DYNAMIC

View File

@@ -0,0 +1,59 @@
#include "Archimedes.h"
#include <steam/steamnetworkingsockets.h>
#include <steam/isteamnetworkingutils.h>
namespace SMEvent {
class DataRecievedEvent : public Archimedes::Event {
public:
DataRecievedEvent() : clientID(0), buffer(nullptr), size(0) {}
DataRecievedEvent(unsigned int c, void* buf, unsigned int s) : clientID(c), buffer(buf), size(s) {}
operator std::string() const { return "DataRecievedEvent"; }
unsigned int clientID;
void* buffer;
unsigned int size;
};
class DataSentEvent : public Archimedes::Event {
public:
DataSentEvent() : clientID(0), buffer(nullptr), size(0) {}
DataSentEvent(unsigned int c, void* buf, unsigned int s) : clientID(c), buffer(buf), size(s) {}
operator std::string() const { return "DataSentEvent"; }
unsigned int clientID;
void* buffer;
unsigned int size;
};
class ConnectionStatusChangedEvent : public Archimedes::Event {
public:
ConnectionStatusChangedEvent() : info(nullptr) {}
ConnectionStatusChangedEvent(SteamNetConnectionStatusChangedCallback_t* i) : info(i) {}
operator std::string() const { return "ConnectionStatusChangedEvent"; }
SteamNetConnectionStatusChangedCallback_t* info;
};
}

View File

@@ -0,0 +1,191 @@
#include "ServerModule.h"
ServerModule::ServerModule(Archimedes::App* a, void* h) : Archimedes::Module(a, h) {
name = "ServerModule";
}
ServerModule::~ServerModule() {
app->removeEventType(SMEvent::DataRecievedEvent());
app->removeEventType(SMEvent::DataSentEvent());
app->removeEventType(SMEvent::ConnectionStatusChangedEvent());
GameNetworkingSockets_Kill();
}
void ServerModule::onLoad() {
app->addEventType(SMEvent::DataRecievedEvent());
app->addEventType(SMEvent::DataSentEvent());
app->addEventType(SMEvent::ConnectionStatusChangedEvent());
SteamDatagramErrMsg errMsg;
if ( !GameNetworkingSockets_Init( nullptr, errMsg ) ) {
std::cerr << "GameNetworkingSockets_Init() Failed: " << errMsg << std::endl;
}
//SteamNetworkingUtils()->SetDebugOutputFunction( k_ESteamNetworkingSocketsDebugOutputType_Msg, DebugOutput );
interface = SteamNetworkingSockets();
SteamNetworkingIPAddr serverLocalAddr;
serverLocalAddr.Clear();
serverLocalAddr.m_port = port;
SteamNetworkingConfigValue_t opt;
opt.SetPtr(k_ESteamNetworkingConfig_Callback_ConnectionStatusChanged, (void*)SteamNetConnectionStatusChangedCallback);
listenSock = interface->CreateListenSocketIP( serverLocalAddr, 1, &opt );
if ( listenSock == k_HSteamListenSocket_Invalid )
std::cerr << "Failed to listen on port " << port << std::endl;
pollGroup = interface->CreatePollGroup();
if ( pollGroup == k_HSteamNetPollGroup_Invalid )
std::cerr << "Failed to listen on port " << port << std::endl;
//Printf( "Server listening on port %d\n", port );
}
void ServerModule::run() {
if(running) {
PollIncomingMessages();
PollConnectionStateChanges();
PollLocalUserInput();
} else if(port >= 0) {
interface->CloseListenSocket(listenSock);
listenSock = k_HSteamListenSocket_Invalid;
interface->DestroyPollGroup(pollGroup);
pollGroup = k_HSteamNetPollGroup_Invalid;
app->stopModule(getName());
port = -1; //just in case
}
}
void ServerModule::OnSteamNetConnectionStatusChanged( SteamNetConnectionStatusChangedCallback_t *pInfo ) {
app->emitEvent(new SMEvent::ConnectionStatusChangedEvent(pInfo));
}
bool ServerModule::onEvent(const Archimedes::Event& event) {
if(eventsToHandle & SMEventEnum::ConnectionStatusChanged && app->getEventType(event) == app->getEventType("ConnectionStatusChangedEvent")) {
SMEvent::ConnectionStatusChangedEvent& e = (SMEvent::ConnectionStatusChangedEvent&) event;
switch(e.info->m_info.m_eState) {
case k_ESteamNetworkingConnectionState_None:
// NOTE: We will get callbacks here when we destroy connections. You can ignore these.
break;
case k_ESteamNetworkingConnectionState_ClosedByPeer:
case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
{
// Ignore if they were not previously connected. (If they disconnected
// before we accepted the connection.)
if ( e.info->m_eOldState == k_ESteamNetworkingConnectionState_Connected )
{
// Locate the client. Note that it should have been found, because this
// is the only codepath where we remove clients (except on shutdown),
// and connection change callbacks are dispatched in queue order.
auto itClient = clients.find( e.info->m_hConn );
assert( itClient != clients.end() );
// Select appropriate log messages
//const char *pszDebugLogAction;
if ( e.info->m_info.m_eState == k_ESteamNetworkingConnectionState_ProblemDetectedLocally )
{
//pszDebugLogAction = "problem detected locally";
//sprintf( temp, "Alas, %s hath fallen into shadow. (%s)", itClient->second.name.c_str(), e.info->m_info.m_szEndDebug );
}
else
{
// Note that here we could check the reason code to see if
// it was a "usual" connection or an "unusual" one.
//pszDebugLogAction = "closed by peer"; (void)pszDebugLogAction;
//sprintf( temp, "%s hath departed", itClient->second.name.c_str() );
}
// Spew something to our own log. Note that because we put their nick
// as the connection description, it will show up, along with their
// transport-specific data (e.g. their IP address)
/*Printf( "Connection %s %s, reason %d: %s\n",
e.info->m_info.m_szConnectionDescription,
pszDebugLogAction,
e.info->m_info.m_eEndReason,
e.info->m_info.m_szEndDebug
);*/
clients.erase( itClient );
// Send a message so everybody else knows what happened
//SendStringToAllClients( temp );
}
else
{
assert( e.info->m_eOldState == k_ESteamNetworkingConnectionState_Connecting );
}
// Clean up the connection. This is important!
// The connection is "closed" in the network sense, but
// it has not been destroyed. We must close it on our end, too
// to finish up. The reason information do not matter in this case,
// and we cannot linger because it's already closed on the other end,
// so we just pass 0's.
interface->CloseConnection( e.info->m_hConn, 0, nullptr, false );
break;
}
case k_ESteamNetworkingConnectionState_Connecting:
{
// This must be a new connection
assert( clients.find( e.info->m_hConn ) == clients.end() );
//Printf( "Connection request from %s", e.info->m_info.m_szConnectionDescription );
// A client is attempting to connect
// Try to accept the connection.
if ( interface->AcceptConnection( e.info->m_hConn ) != k_EResultOK )
{
// This could fail. If the remote host tried to connect, but then
// disconnected, the connection may already be half closed. Just
// destroy whatever we have on our side.
interface->CloseConnection( e.info->m_hConn, 0, nullptr, false );
//Printf( "Can't accept connection. (It was already closed?)" );
break;
}
// Assign the poll group
if ( !interface->SetConnectionPollGroup( e.info->m_hConn, pollGroup ) )
{
interface->CloseConnection( e.info->m_hConn, 0, nullptr, false );
//Printf( "Failed to set poll group?" );
break;
}
// Add them to the client list, using std::map wacky syntax
clients[ e.info->m_hConn ] = ++numClients;
//SetClientNick( e.info->m_hConn, nick );
break;
}
case k_ESteamNetworkingConnectionState_Connected:
// We will get a callback immediately after accepting the connection.
// Since we are the server, we can ignore this, it's not news to us.
break;
default:
// Silences -Wswitch
break;
}
return true;
} else if(eventsToHandle & SMEventEnum::DataRecieved && app->getEventType(event) == app->getEventType("DataRecievedEvent")) {
return true;
} else if(eventsToHandle & SMEventEnum::DataSent && app->getEventType(event) == app->getEventType("DataSentEvent")) {
return true;
}
return false;
}

View File

@@ -0,0 +1,63 @@
#include "Archimedes.h"
#include <steam/steamnetworkingsockets.h>
#include <steam/isteamnetworkingutils.h>
#include "ServerEvents.h"
class ServerModule : public Archimedes::Module {
public:
ServerModule(Archimedes::App*, void*);
~ServerModule();
void onLoad();
bool onEvent(const Archimedes::Event&);
void run();
void startServer(int);
void stopServer();
enum SMEventEnum {
None = 0,
ConnectionStatusChanged = 1 << 0,
DataRecieved = 1 << 1,
DataSent = 1 << 2
};
void shouldHandleEvents(unsigned int events) { eventsToHandle = events; }
void sendData() {}
void pollIncomingData();
private:
//handle all events by default
unsigned int eventsToHandle = SMEventEnum::ConnectionStatusChanged | SMEventEnum::DataSent | SMEventEnum::DataRecieved;
bool running = false;
int port = -1;
unsigned int numClients = 0;
HSteamListenSocket listenSock;
HSteamNetPollGroup pollGroup;
ISteamNetworkingSockets* interface;
inline static ServerModule *callbackInstance = nullptr;
static void SteamNetConnectionStatusChangedCallback( SteamNetConnectionStatusChangedCallback_t *pInfo ) {
callbackInstance->OnSteamNetConnectionStatusChanged( pInfo );
}
void OnSteamNetConnectionStatusChanged( SteamNetConnectionStatusChangedCallback_t *pInfo );
std::map<HSteamNetConnection, unsigned int> clients;
void PollConnectionStateChanges();
void PollLocalUserInput();
};
#ifdef SERVERMODULE_DYNAMIC
#define MODULE_TYPE ServerModule
#include "endModule.h"
#endif

View File

@@ -6,8 +6,6 @@ class Print : public Archimedes::Module {
Print(Archimedes::App*, void*); Print(Archimedes::App*, void*);
~Print(); ~Print();
void run(); void run();
void onLoad() {}
}; };
#ifdef PRINT_DYNAMIC #ifdef PRINT_DYNAMIC

View File

@@ -30,7 +30,7 @@ void TestImgui::run() {
if(demo) if(demo)
ImGui::ShowDemoWindow(&this->demo); ImGui::ShowDemoWindow(&this->demo);
else else
app->end(); app->stopModule(getName());
{ {
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();

View File

@@ -0,0 +1,28 @@
#include "TestNotCurses.h"
TestNotCurses::TestNotCurses(Archimedes::App* a, void* h) : Archimedes::Module(a, h) {
name = "TestNotCurses";
}
TestNotCurses::~TestNotCurses() {
notcurses_stop(ncInstance);
}
void TestNotCurses::onLoad() {
setlocale(LC_ALL, "");
notcurses_options opts;
ncInstance = notcurses_init(&opts, NULL);
if(!ncInstance) {
app->stopModule(getName());
return;
}
}
void TestNotCurses::run() {
notcurses_render(ncInstance);
}

View File

@@ -0,0 +1,19 @@
#include "Archimedes.h"
#include <notcurses/notcurses.h>
class TestNotCurses : public Archimedes::Module {
public:
TestNotCurses(Archimedes::App*, void*);
~TestNotCurses();
void run();
void onLoad();
private:
notcurses* ncInstance;
};
#ifdef TESTNOTCURSES_DYNAMIC
#define MODULE_TYPE TestNotCurses
#include "endModule.h"
#endif