Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ cd Framework
```

### Build on macOS/Linux

On Debian/Ubuntu, install the required system development packages first:

```sh
sudo apt install build-essential cmake \
libssl-dev zlib1g-dev libssh2-1-dev libcurl4-openssl-dev
```

Equivalents on other distributions: `openssl-devel`, `zlib-devel`, `libssh2-devel`, `libcurl-devel` (Fedora/RHEL).

```
# Configure CMake project
cmake -B build
Expand Down
5 changes: 5 additions & 0 deletions code/framework/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ set(FRAMEWORK_SERVER_SRC
src/integrations/server/instance.cpp
src/integrations/server/networking/engine.cpp

# Native plugin system (server-side)
src/integrations/server/plugins/loader.cpp
src/integrations/server/plugins/host_impl.cpp
src/integrations/server/plugins/manager.cpp

# JavaScript scripting (server module)
src/integrations/server/scripting/module.cpp
)
Expand Down
53 changes: 50 additions & 3 deletions code/framework/src/integrations/server/instance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ namespace Framework::Integrations::Server {
_masterlist = std::make_unique<Services::MasterlistConnector>();
_commandListener = std::make_unique<Utils::CommandListener>();
_commandProcessor = std::make_unique<Utils::CommandProcessor>();
_pluginManager = std::make_unique<Plugins::PluginManager>();
}

Instance::~Instance() {
Expand Down Expand Up @@ -139,7 +140,14 @@ namespace Framework::Integrations::Server {

// Initialize mod subsystems
PostInit();


// Native plugins load after PostInit so the mod has had a chance to
// register subsystems plugins may depend on, but before the scripting
// engine comes up so plugins can register scripting builtins from
// their PostScriptInit hook.
_pluginManager->Init(_webServer.get(), _commandProcessor.get(), _worldEngine.get());
_pluginManager->LoadAll(_opts.modulesDir, _opts.modulesList);

const auto sdkCallback = [this](Framework::Scripting::Engine *engine) {
this->RegisterScriptingBuiltins(engine);
};
Expand All @@ -154,6 +162,7 @@ namespace Framework::Integrations::Server {
CoreModules::SetScriptingModule(_scriptingModule.get());

PostScriptInit();
_pluginManager->PostScriptInit();

// Discover resources
_scriptingModule->GetResourceManager()->DiscoverResources();
Expand Down Expand Up @@ -231,6 +240,22 @@ namespace Framework::Integrations::Server {
_opts.bindMapName = _fileConfig->Get<std::string>("map");
_opts.maxPlayers = _fileConfig->Get<int>("maxplayers");
_opts.bindSecretKey = _fileConfig->Get<std::string>("server-token");

// Plugin loading: optional. modules_dir defaults to "modules"
// relative to the server's working directory; modules is the
// ordered list of plugin names to load.
_opts.modulesDir = _fileConfig->GetDefault<std::string>("modules_dir", _opts.modulesDir);
if (auto *doc = _fileConfig->GetDocument(); doc && doc->contains("modules")) {
if ((*doc)["modules"].is_array()) {
_opts.modulesList.clear();
for (const auto &m : (*doc)["modules"]) {
if (m.is_string()) _opts.modulesList.push_back(m.get<std::string>());
}
}
else {
Logging::GetLogger(FRAMEWORK_INNER_SERVER)->warn("server.json 'modules' field exists but is not an array; ignoring");
}
}
}
catch (const std::exception &ex) {
Logging::GetLogger(FRAMEWORK_INNER_SERVER)->critical("JSON config has missing fields: {}", ex.what());
Expand Down Expand Up @@ -314,6 +339,9 @@ namespace Framework::Integrations::Server {
if (_onPlayerDisconnectCallback)
_onPlayerDisconnectCallback(e, guid.g);

if (_pluginManager)
_pluginManager->DispatchPlayerDisconnect(e.id(), guid.g);

_worldEngine->RemoveEntity(e);
}

Expand Down Expand Up @@ -345,8 +373,12 @@ namespace Framework::Integrations::Server {

net->RegisterMessage<ClientInitPlayer>(Framework::Networking::Messages::GameMessages::GAME_INIT_PLAYER, [this, net](SLNet::RakNetGUID guid, ClientInitPlayer *stub) {
const auto e = _worldEngine->GetEntityByGUID(guid.g);
if (_onPlayerConnectCallback && e.is_valid() && e.is_alive())
_onPlayerConnectCallback(e, guid.g);
if (e.is_valid() && e.is_alive()) {
if (_onPlayerConnectCallback)
_onPlayerConnectCallback(e, guid.g);
if (_pluginManager)
_pluginManager->DispatchPlayerConnect(e.id(), guid.g);
}
});

// Note: Client-to-server events are handled through the JS Events system
Expand Down Expand Up @@ -500,6 +532,10 @@ namespace Framework::Integrations::Server {

PreShutdown();

if (_pluginManager) {
_pluginManager->PreShutdown();
}

if (_scriptingModule) {
_scriptingModule->PreShutdown();
}
Expand All @@ -516,6 +552,13 @@ namespace Framework::Integrations::Server {
_webServer->Shutdown();
}

// Plugin libraries are unloaded last so any in-flight HTTP requests
// handled by plugin trampolines have already finished with the
// webserver stopped above.
if (_pluginManager) {
_pluginManager->Shutdown();
}

if (_worldEngine) {
_worldEngine->Shutdown();
}
Expand Down Expand Up @@ -555,6 +598,10 @@ namespace Framework::Integrations::Server {
_commandListener->Update();
}

if (_pluginManager) {
_pluginManager->Update(_opts.worldConfig.tickInterval);
}

if (_masterlist->IsInitialized()) {
Services::ServerInfo info {};
info.port = _opts.bindPort;
Expand Down
13 changes: 13 additions & 0 deletions code/framework/src/integrations/server/instance.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "http/webserver.h"
#include "logging/logger.h"
#include "networking/engine.h"
#include "plugins/manager.h"
#include "scripting/module.h"
#include "services/masterlist.h"
#include "utils/config.h"
Expand Down Expand Up @@ -70,6 +71,13 @@ namespace Framework::Integrations::Server {
int32_t maxPlayers;
std::string httpServeDir;

// Native plugins. modulesDir is searched for each name in modulesList;
// a plugin called "foo" lives at <modulesDir>/foo/foo.module.json plus
// its compiled library. Both fields are populated from server.json
// ("modules_dir" and "modules") and overridable at code level.
std::string modulesDir = "modules";
std::vector<std::string> modulesList;

bool enableSignals;

// update intervals
Expand Down Expand Up @@ -98,6 +106,7 @@ namespace Framework::Integrations::Server {
std::unique_ptr<Services::MasterlistConnector> _masterlist;
std::unique_ptr<Utils::CommandListener> _commandListener;
std::unique_ptr<Utils::CommandProcessor> _commandProcessor;
std::unique_ptr<Plugins::PluginManager> _pluginManager;

void InitEndpoints();
void InitModules() const;
Expand Down Expand Up @@ -181,6 +190,10 @@ namespace Framework::Integrations::Server {
World::Archetypes::StreamingFactory *GetStreamingFactory() const {
return _streamingFactory.get();
}

Plugins::PluginManager *GetPluginManager() const {
return _pluginManager.get();
}

// Register a custom command with the command processor
Utils::Result<std::string, Utils::CommandProcessorError> RegisterCommand(std::string_view name, std::initializer_list<cxxopts::Option> options, const Utils::CommandProc &proc, const std::string &desc) {
Expand Down
Loading
Loading