From b31c289d6e67fe17736bc0bd4342dbffeeb6eaa4 Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 22 Oct 2026 22:10:08 +0800 Subject: [PATCH 1/2] refactor(examples)!: use typed HTTP plugin construction Hide HTTP sample implementations in their cpp files, register owned interfaces through private factories and export plugin entries explicitly. Preserve the disabled application-example selection and the existing request/listener flow. Exercise real example libraries through core validation when their public dependencies are available, with synchronized bilingual usage and migration notes. BREAKING CHANGE: the HTTP server sample interface is now http_server_sample_module, and both example implementation classes are private. Rebuild the example libraries and host together and use typed plugin lookup instead of constructing example implementations directly. --- docs/guides/examples.md | 5 +- docs/guides/examples_CN.md | 5 +- docs/reference/build-options.md | 3 +- docs/reference/build-options_CN.md | 3 +- docs/reference/http.md | 44 +++ docs/reference/http_CN.md | 30 ++ examples/http_client/CMakeLists.txt | 8 +- .../http_client/http_client_sample_module.cpp | 42 ++- .../http_client/http_client_sample_module.hpp | 14 +- .../http_client/http_client_sample_plugin.cpp | 22 +- .../http_client/http_client_sample_plugin.hpp | 16 +- examples/http_server/CMakeLists.txt | 8 +- examples/http_server/http_server_module.cpp | 30 +- examples/http_server/http_server_module.hpp | 12 +- examples/http_server/http_server_plugin.cpp | 22 +- examples/http_server/http_server_plugin.hpp | 16 +- tests/app/http_examples.cmake | 9 + tests/app/http_examples.cpp | 292 ++++++++++++++++++ tests/ci/http-examples.test.mjs | 69 +++++ tests/ci/plugin-names.test.mjs | 4 +- tests/core/CMakeLists.txt | 26 ++ tests/core/README.md | 12 + tests/core/README_CN.md | 8 + 23 files changed, 641 insertions(+), 59 deletions(-) create mode 100644 tests/app/http_examples.cmake create mode 100644 tests/app/http_examples.cpp create mode 100644 tests/ci/http-examples.test.mjs diff --git a/docs/guides/examples.md b/docs/guides/examples.md index aa24406f4..17624d473 100644 --- a/docs/guides/examples.md +++ b/docs/guides/examples.md @@ -8,7 +8,8 @@ The bundled examples use capability names. Configure with applications and examp | `examples/module_lifecycle` / `module_lifecycle` | `module_lifecycle_plugin` | Lifecycle and a small data-list demonstration; enabled, no update override. The callback demonstration remains commented out | | `examples/entity_operations` / `entity_operations` | `entity_operations_plugin` | Kernel entity/data/table operations; enabled, no update override. Startup performs a bounded functional demonstration; the [old timed workload](../../benchmarks/ENTITY_OPERATIONS.md) runs only through an explicit benchmark command | | `examples/physics_scene` / `physics_scene` | `physics_scene_plugin` | Disabled; requires unsupported legacy PhysX 3. The checked-in sources and manifest do not establish a runnable integration | -| `examples/http_server` / `http_server` | `http_server_plugin` | Disabled; retained HTTP server example, without runtime acceptance | +| `examples/http_server` / `http_server` | `http_server_plugin` | Disabled as an application example; [typed construction and controlled DSO regression](../reference/http.md#legacy-http-example-construction) | +| `examples/http_client` / `http_client_sample` | `http_client_sample_plugin` | Disabled as an application example; future/callback POST and etcd lease demonstration with fixed external endpoints | hpp/cpp files remain adjacent in each directory. Start with the [timers quick start](../get-started/quick-start.md). The generic launcher helpers select `conf/.app.toml`, so use the capability name for both the process and application manifest. Kernel examples still require their business metadata. @@ -24,7 +25,7 @@ hpp/cpp files remain adjacent in each directory. Start with the [timers quick st Rebuild modules and their consumers together: the C++ module/plugin type names and RTTI registration keys change. Replace old library stems such as `sample1_plugin` with `timers_plugin`, including platform extensions and Debug `_d`. Replace staged `conf/sample1.app.toml` with `conf/timers.app.toml` and update `--name`, `--app_conf`, launcher arguments and deployment ConfigMaps consistently for every row. The old targets, library aliases and configuration filenames are not emitted. An existing runtime directory is not migrated automatically; stage a fresh matching executable, plugin set and configuration before switching it over. -The byte-preserved historical documentation archive retains original names and commands. It is not the current naming guide. This page records names and supported status, not new runtime or performance acceptance. +The byte-preserved historical documentation archive retains original names and commands. It is not the current naming guide. This page records names and supported status; the HTTP example fixture uses public service doubles and does not establish external-service or performance acceptance. ## Run entity operations diff --git a/docs/guides/examples_CN.md b/docs/guides/examples_CN.md index 61a9ba732..dcf5e9936 100644 --- a/docs/guides/examples_CN.md +++ b/docs/guides/examples_CN.md @@ -8,7 +8,8 @@ | `examples/module_lifecycle`/`module_lifecycle` | `module_lifecycle_plugin` | 生命周期与少量 data-list 演示;启用,无 update 覆写。回调演示代码仍被注释 | | `examples/entity_operations`/`entity_operations` | `entity_operations_plugin` | Kernel 实体、数据和表操作;启用,无 update 覆写。启动仅做有界功能演示;[旧计时负载](../../benchmarks/ENTITY_OPERATIONS_CN.md)只能通过显式基准命令运行 | | `examples/physics_scene`/`physics_scene` | `physics_scene_plugin` | 关闭,依赖不支持的旧 PhysX 3;保留源码与清单不代表集成可运行 | -| `examples/http_server`/`http_server` | `http_server_plugin` | 关闭,保留 HTTP 服务示例,尚无运行验收 | +| `examples/http_server`/`http_server` | `http_server_plugin` | 默认应用示例中关闭;支持[类型化创建与受控动态库回归](../reference/http_CN.md#旧-http-示例的类型化创建) | +| `examples/http_client`/`http_client_sample` | `http_client_sample_plugin` | 默认应用示例中关闭;使用固定外部端点演示 future/回调 POST 与 etcd 租约 | 每个目录中的 hpp/cpp 继续并排存放。可从 [timers 快速开始](../get-started/quick-start_CN.md)入手。 通用启动脚本选择 `conf/.app.toml`,因此进程名称和应用清单都应使用能力名称。Kernel 示例仍需要业务元数据。 @@ -28,7 +29,7 @@ 将已部署的 `conf/sample1.app.toml` 换为 `conf/timers.app.toml`,并按上表逐项同步 `--name`、`--app_conf`、启动脚本参数和部署 ConfigMap。 不再生成旧 target、旧库别名或旧配置文件名。已有运行目录不会自动迁移;切换前应重新准备同次构建的可执行文件、插件集与配置。 -按字节保存的历史文档归档仍保留原始名称和命令,不作为当前命名指南。本页说明名称和支持边界,不代表新增运行或性能验收。 +按字节保存的历史文档归档仍保留原始名称和命令,不作为当前命名指南。本页说明名称与支持边界;HTTP 示例测试使用公开服务替身,不代表外部服务或性能验收。 ## 运行实体操作示例 diff --git a/docs/reference/build-options.md b/docs/reference/build-options.md index d3a868b4e..667643b4a 100644 --- a/docs/reference/build-options.md +++ b/docs/reference/build-options.md @@ -9,6 +9,7 @@ | `ARK_BUILD_TESTS` | `ON` | Build legacy test programs | | `ARK_BUILD_CORE_TESTS` | Value of `ARK_BUILD_TESTS` | Build C++20 core and optional Protobuf regressions | | `ARK_BUILD_EXAMPLES` | `ON` | Build enabled examples | +| `ARK_BUILD_HTTP_EXAMPLE_TESTS` | `ON` for core tests with Boost and HTTP/Net/Etcd Client API targets, otherwise `OFF` | Compile and run actual HTTP example DSOs with isolated service doubles; explicit `OFF` is respected | | `ARK_BUILD_PLUGIN_MYSQL` | `OFF` | Build the native MySQL coroutine plugin; requires `ARKNET_ENABLE_SSL=ON` | | `ARK_BUILD_PLUGIN_REDIS` | `OFF` | Build the native Redis coroutine plugin; enable SSL for TLS pools | | `ARK_BUILD_DATABASE_TESTS` | `OFF` | Build native database and public plugin regressions; requires an explicit `ARKNET_SOURCE_DIR` | @@ -24,7 +25,7 @@ When upgrading arknet, use a new build directory or explicitly change the cached Official plugin options use `ARK_BUILD_PLUGIN_`. The default runtime selection is `kernel`, `net`, `bus`, `utility` and `etcd_client`; enable the optional MySQL/Redis targets to build native coroutine plugins. Protocol and plugin-load tests do not replace real-server deployment acceptance. See [project layout](directory-structure.md), [protocol generation](protobuf.md) and [network builds](../networking.md). -With `ARK_BUILD_EXAMPLES` enabled, the [coroutine task example](../guides/tasks.md) also builds with applications disabled. Existing plugin examples still require application builds. +With `ARK_BUILD_EXAMPLES` enabled, the [coroutine task example](../guides/tasks.md) also builds with applications disabled. Enabled application plugin examples still require application builds. HTTP example construction tests can run separately with core tests; see [HTTP examples](http.md#legacy-http-example-construction). See [MySQL](mysql.md) and [Redis](redis.md) for plugin configuration and coroutine APIs. diff --git a/docs/reference/build-options_CN.md b/docs/reference/build-options_CN.md index 32b3aacb9..b8aeba21e 100644 --- a/docs/reference/build-options_CN.md +++ b/docs/reference/build-options_CN.md @@ -9,6 +9,7 @@ | `ARK_BUILD_TESTS` | `ON` | 构建原有测试程序 | | `ARK_BUILD_CORE_TESTS` | 与 `ARK_BUILD_TESTS` 一致 | 构建 C++20 核心与可选的 Protobuf 回归测试 | | `ARK_BUILD_EXAMPLES` | `ON` | 构建选中的示例 | +| `ARK_BUILD_HTTP_EXAMPLE_TESTS` | 核心测试使用 Boost 且具备 HTTP/Net/Etcd Client API target 时为 `ON`,否则为 `OFF` | 通过独立服务替身编译并运行真实 HTTP 示例动态库;保留显式 `OFF` | | `ARK_BUILD_PLUGIN_MYSQL` | `OFF` | 构建原生 MySQL 协程插件,要求 `ARKNET_ENABLE_SSL=ON` | | `ARK_BUILD_PLUGIN_REDIS` | `OFF` | 构建原生 Redis 协程插件,TLS 连接池需要开启 SSL | | `ARK_BUILD_DATABASE_TESTS` | `OFF` | 构建原生数据库及公开插件回归,要求显式设置 `ARKNET_SOURCE_DIR` | @@ -24,7 +25,7 @@ 官方插件选项采用 `ARK_BUILD_PLUGIN_`。默认运行时插件为 `kernel`、`net`、`bus`、`utility` 和 `etcd_client`;启用 MySQL 或 Redis 构建选项可使用原生协程插件;本机协议测试与插件加载检查不能替代真实数据库部署验收。参见[项目结构](directory-structure_CN.md)、[协议生成](protobuf_CN.md)与[网络构建](../networking_CN.md)。 -开启 `ARK_BUILD_EXAMPLES` 时,[协程任务示例](../guides/tasks_CN.md)可以在关闭应用的情况下独立构建;其余现有插件示例仍要求开启应用构建。 +开启 `ARK_BUILD_EXAMPLES` 时,[协程任务示例](../guides/tasks_CN.md)可以在关闭应用的情况下独立构建;默认启用的应用插件示例仍要求开启应用构建。HTTP 示例创建测试可以随核心测试单独运行,详见 [HTTP 示例](http_CN.md#旧-http-示例的类型化创建)。 数据库插件的配置与接口见 [MySQL](mysql_CN.md) 和 [Redis](redis_CN.md)。 diff --git a/docs/reference/http.md b/docs/reference/http.md index 5c2a31f5c..7e0ad5f90 100644 --- a/docs/reference/http.md +++ b/docs/reference/http.md @@ -106,3 +106,47 @@ factory. `tests/consumer/http` links only `ark::http`, compiles the public heade separately and rejects private header paths with apps/Protobuf disabled. See [directory structure](directory-structure.md) for the public-header consumer command, and [arknet HTTP](https://openarkstudio.github.io/arknet/features/) for routing APIs. + +## Legacy HTTP example construction + +`examples/http_server` and `examples/http_client` remain excluded from default +example builds. Their adjacent sample interface headers support typed lookup; +concrete classes are declared only in cpp and inherit `module_base` of the sample +interface. Private factories return owned interfaces to `plugin_base`, which +exports explicit `DllEntryPlugin` / `DllExitPlugin` entries and registers both +samples with `module_update::disabled`, preserving their inherited update behavior. +The server sample uses `http_server_sample_module` to avoid its old collision with +the public `http_server_module` service. Library stems remain `http_server_plugin` +and `http_client_sample_plugin`; installed public headers remain unchanged. +Rebuild the example DSOs and their host together. Source code that instantiated +an example implementation must instead load the plugin and look up its sample +interface; the server sample lookup now uses `http_server_sample_module`. + +The server still requests `0.0.0.0:8080` with four IO threads and returns success +from `post_init()` after calling `start_server()`, including when that call rejects +the listener. The client still performs a future POST and a callback POST to +`172.20.3.74:2379/v3/kv/put` with the original JSON body and HTTP timeout defaults +(20 seconds IO, 5 seconds connect), then grants a 100-second lease through an etcd +client using the existing 5/2-second timeout defaults. A null dependency or client +result makes `init()` return `false` before dereferencing it; Debug builds retain +the manager's assertions for missing module lookup. These +fixed endpoints remain demonstrations that need an appropriate external setup. +IO, transport and request completion belong to arknet; callbacks keep the public +HTTP client's manager-update contract. + +Core-test configurations default `ARK_BUILD_HTTP_EXAMPLE_TESTS` to `ON` when +Boost.Asio is selected and `ark::http`, `ark::net` and `ark::etcd_client` API +targets exist; otherwise the default is `OFF`. Explicit `OFF` remains supported. +Existing Applications CI satisfies those prerequisites and runs the fixture via +`check-core`; the default application-example selection stays unchanged. +For an isolated construction regression, configure with +`-DASIO_STANDALONE=OFF -DARK_BUILD_CORE_TESTS=ON -DARK_BUILD_HTTP_EXAMPLE_TESTS=ON`, +then run `cmake --build core-build --target check-http-examples --parallel 2`. +The test target compiles both actual example modules and DSOs, calls their +factories, loads their exported entries and verifies interface identity, +configuration selection, POST/lease arguments and timeouts, owner-update callback +delivery, listener arguments, lease-owner destruction, repeated stop and reload. +Public service doubles make these checks deterministic without listening or +contacting etcd. They establish example construction and call contracts; real HTTP +wire coverage remains in `check-http-plugin` / `check-network`, and this fixture +does not establish external etcd acceptance. diff --git a/docs/reference/http_CN.md b/docs/reference/http_CN.md index 9070de145..cda3745a6 100644 --- a/docs/reference/http_CN.md +++ b/docs/reference/http_CN.md @@ -81,3 +81,33 @@ bool listen(ark::plugin_manager& manager) { 既有 37 用例 `check-network` 通过私有工厂保留 HTTP 重复绑定、重启、公开响应助手与无效 TOML 断言。 独立的 `tests/consumer/http` 仅链接 `ark::http`,在关闭应用/Protobuf 时分别编译公开头并拒绝私有头路径。 公共接口头消费命令见[目录结构](directory-structure_CN.md),路由 API 见 [arknet HTTP](https://openarkstudio.github.io/arknet/zh/features/)。 + +## 旧 HTTP 示例的类型化创建 + +`examples/http_server` 与 `examples/http_client` 默认仍不参与示例构建。相邻的示例接口头支持类型化查找; +具体类仅在 cpp 中声明,继承对应示例接口的 `module_base`。私有工厂向 `plugin_base` 返回拥有型接口, +插件显式导出 `DllEntryPlugin`/`DllExitPlugin`,两个示例均以 `module_update::disabled` 注册, +保留继承的默认更新行为。服务端示例使用 `http_server_sample_module`,消除旧类名与公开服务 +`http_server_module` 的冲突。动态库名仍为 `http_server_plugin` 和 `http_client_sample_plugin`, +已安装的公共头文件不变。 +示例动态库和宿主需要一起重新编译。原来直接创建示例实现类的代码应改为加载插件后查找示例接口; +服务端示例改用 `http_server_sample_module` 查找。 + +服务端仍以四个 IO 线程请求监听 `0.0.0.0:8080`;`post_init()` 调用 `start_server()` 后仍返回成功, +包括监听启动被拒绝的情况。客户端仍向 `172.20.3.74:2379/v3/kv/put` 发送 future POST 和回调 POST, +保留原 JSON 正文、HTTP 超时默认值(IO 20 秒、连接 5 秒),再通过 etcd 客户端申请 100 秒租约; +etcd 客户端的超时默认值仍为 IO 5 秒、连接 2 秒。依赖或客户端返回空指针时,`init()` 会在解引用前 +返回 `false`;Debug 构建保留管理器对缺失模块查找的断言。这些固定端点仍是需要外部环境的演示配置。 +IO、传输和请求完成继续由 arknet 负责; +回调遵循公开 HTTP 客户端由管理器 update 派发的约定。 + +核心测试配置选择 Boost.Asio 且存在 `ark::http`、`ark::net`、`ark::etcd_client` API target 时, +`ARK_BUILD_HTTP_EXAMPLE_TESTS` 默认 `ON`,否则默认 `OFF`;显式设置 `OFF` 仍然有效。 +既有 Applications CI 满足这些条件,通过 `check-core` 运行该测试;默认应用示例集合不变。 +如需独立验证示例创建,配置 +`-DASIO_STANDALONE=OFF -DARK_BUILD_CORE_TESTS=ON -DARK_BUILD_HTTP_EXAMPLE_TESTS=ON`, +再运行 `cmake --build core-build --target check-http-examples --parallel 2`。 +该测试目标编译两个真实示例模块和动态库,调用工厂并加载导出入口,检查接口身份、配置选择、 +POST/租约参数与超时、所属线程 update 回调派发、监听参数、租约所有者析构、重复停机与重新加载。 +公开服务替身使测试可重复,无需监听端口或连接 etcd。这些检查验证示例创建与调用约定; +真实 HTTP 传输仍由 `check-http-plugin`/`check-network` 覆盖,本测试不证明外部 etcd 验收通过。 diff --git a/examples/http_client/CMakeLists.txt b/examples/http_client/CMakeLists.txt index 3fb85b3ed..fd763ea44 100644 --- a/examples/http_client/CMakeLists.txt +++ b/examples/http_client/CMakeLists.txt @@ -1 +1,7 @@ -BUILD_PLUGIN_MACRO(http_client_sample_plugin) \ No newline at end of file +# Copyright (c) 2026 OpenArkStudio. SPDX-License-Identifier: Apache-2.0 +add_library(http_client_sample_plugin SHARED http_client_sample_module.cpp http_client_sample_plugin.cpp) +set_property(TARGET http_client_sample_plugin PROPERTY LINK_LIBRARIES ark::net ark::etcd_client) +set_target_properties(http_client_sample_plugin PROPERTIES PREFIX "" + ARCHIVE_OUTPUT_DIRECTORY "${BIN_OUTPUT_DIR}" + RUNTIME_OUTPUT_DIRECTORY "${BIN_OUTPUT_DIR}" + LIBRARY_OUTPUT_DIRECTORY "${BIN_OUTPUT_DIR}") diff --git a/examples/http_client/http_client_sample_module.cpp b/examples/http_client/http_client_sample_module.cpp index 35ad8aa0b..af6b4cb05 100644 --- a/examples/http_client/http_client_sample_module.cpp +++ b/examples/http_client/http_client_sample_module.cpp @@ -18,22 +18,46 @@ * */ -#include +#include +#include +#include +#include +#include -#include "http_client_sample_module.hpp" +#include "ark/etcd_client/etcd_client_module.hpp" +#include "ark/module_base.hpp" +#include "ark/net/http_client_module.hpp" +#include "http_client_sample_plugin.hpp" using namespace std; namespace ark { +namespace { -bool http_client_sample_module::init() { +class http_client_sample_module_impl final : public module_base +{ +public: + bool init() override; + bool post_init() override; + +private: + http_client_module* http_client_{}; + std::shared_ptr etcd_client_; +}; + +bool http_client_sample_module_impl::init() { http_client_ = find_module(); - etcd_client_ = find_module()->create_etcd_client( - vector{"172.20.3.74:2379" /*, "172.20.3.75:2379", "172.20.3.76:2379" */}); + auto* etcd = find_module(); + if (!http_client_ || !etcd) + { + return false; + } + etcd_client_ = + etcd->create_etcd_client(vector{"172.20.3.74:2379" /*, "172.20.3.75:2379", "172.20.3.76:2379" */}); return http_client_ != nullptr && etcd_client_ != nullptr; } -bool http_client_sample_module::post_init() { +bool http_client_sample_module_impl::post_init() { std::cout << "Start testing Http Client" << endl; string body = "{\"key\": \"Zm9v\", \"value\": \"YmFy\"}"; @@ -53,4 +77,10 @@ bool http_client_sample_module::post_init() { return true; } +} // namespace + +std::unique_ptr make_http_client_sample_module() { + return std::make_unique(); +} + } // namespace ark diff --git a/examples/http_client/http_client_sample_module.hpp b/examples/http_client/http_client_sample_module.hpp index 7d692c127..8ff200545 100644 --- a/examples/http_client/http_client_sample_module.hpp +++ b/examples/http_client/http_client_sample_module.hpp @@ -20,22 +20,12 @@ #pragma once -#include "ark/etcd_client/etcd_client_module.hpp" -#include "ark/net/http_client_module.hpp" -#include "ark/plugin_manager.hpp" +#include "ark/module.hpp" namespace ark { -class http_client_sample_module final : public module +class http_client_sample_module : public module { - ARK_DECLARE_MODULE_FUNCTIONS -private: - http_client_module* http_client_; - std::shared_ptr etcd_client_; - -public: - bool init() override; - bool post_init() override; }; } // namespace ark diff --git a/examples/http_client/http_client_sample_plugin.cpp b/examples/http_client/http_client_sample_plugin.cpp index 211fb86d6..8896d1131 100644 --- a/examples/http_client/http_client_sample_plugin.cpp +++ b/examples/http_client/http_client_sample_plugin.cpp @@ -18,19 +18,33 @@ * */ -#include "http_client_sample_module.hpp" +#include + #include "http_client_sample_plugin.hpp" namespace ark { -ARK_DECLARE_PLUGIN_DLL_FUNCTION(http_client_sample_plugin) +ARK_EXPORT_FUNC void DllEntryPlugin(plugin_manager* manager, const std::string& plugin_name, logger* log) { + logger::init(log); + manager->register_plugin(plugin_name); +} + +ARK_EXPORT_FUNC void DllExitPlugin(plugin_manager* manager) { + manager->unregister_plugin(); +} void http_client_sample_plugin::install() { - ARK_REGISTER_MODULE(http_client_sample_module, http_client_sample_module); + if (!register_module(make_http_client_sample_module(), module_update::disabled)) + { + throw std::runtime_error("http_client example module registration rejected"); + } } void http_client_sample_plugin::uninstall() { - ARK_UNREGISTER_MODULE(http_client_sample_module, http_client_sample_module); + if (!unregister_module()) + { + throw std::runtime_error("http_client example module unregistration rejected"); + } } } // namespace ark diff --git a/examples/http_client/http_client_sample_plugin.hpp b/examples/http_client/http_client_sample_plugin.hpp index bae3287d1..1c3739654 100644 --- a/examples/http_client/http_client_sample_plugin.hpp +++ b/examples/http_client/http_client_sample_plugin.hpp @@ -20,12 +20,20 @@ #pragma once -#include "ark/module.hpp" -#include "ark/plugin.hpp" -#include "ark/plugin_manager.hpp" +#include + +#include "ark/plugin_base.hpp" +#include "http_client_sample_module.hpp" namespace ark { -ARK_DECLARE_PLUGIN(http_client_sample_plugin) +std::unique_ptr make_http_client_sample_module(); + +class http_client_sample_plugin final : public plugin_base +{ +public: + void install() override; + void uninstall() override; +}; } // namespace ark diff --git a/examples/http_server/CMakeLists.txt b/examples/http_server/CMakeLists.txt index 7b1b1b983..96e50a5bf 100644 --- a/examples/http_server/CMakeLists.txt +++ b/examples/http_server/CMakeLists.txt @@ -1 +1,7 @@ -BUILD_PLUGIN_MACRO(http_server_plugin) \ No newline at end of file +# Copyright (c) 2026 OpenArkStudio. SPDX-License-Identifier: Apache-2.0 +add_library(http_server_plugin SHARED http_server_module.cpp http_server_plugin.cpp) +set_property(TARGET http_server_plugin PROPERTY LINK_LIBRARIES ark::http) +set_target_properties(http_server_plugin PROPERTIES PREFIX "" + ARCHIVE_OUTPUT_DIRECTORY "${BIN_OUTPUT_DIR}" + RUNTIME_OUTPUT_DIRECTORY "${BIN_OUTPUT_DIR}" + LIBRARY_OUTPUT_DIRECTORY "${BIN_OUTPUT_DIR}") diff --git a/examples/http_server/http_server_module.cpp b/examples/http_server/http_server_module.cpp index 02851a4c7..58c6421f8 100644 --- a/examples/http_server/http_server_module.cpp +++ b/examples/http_server/http_server_module.cpp @@ -18,19 +18,37 @@ * */ -#include "http_server_module.hpp" +#include "ark/http/http_server_module.hpp" +#include "ark/module_base.hpp" +#include "http_server_plugin.hpp" namespace ark { +namespace { -bool http_server_module::init() { - http_server_module_ = find_module(); +class http_server_sample_module_impl final : public module_base +{ +public: + bool init() override; + bool post_init() override; - return true; +private: + http_server_module* http_server_{}; +}; + +bool http_server_sample_module_impl::init() { + http_server_ = find_module(); + return http_server_ != nullptr; } -bool http_server_module::post_init() { - http_server_module_->start_server("0.0.0.0", 8080, 4); +bool http_server_sample_module_impl::post_init() { + http_server_->start_server("0.0.0.0", 8080, 4); return true; } +} // namespace + +std::unique_ptr make_http_server_sample_module() { + return std::make_unique(); +} + } // namespace ark diff --git a/examples/http_server/http_server_module.hpp b/examples/http_server/http_server_module.hpp index bcc40aa8f..d3636100b 100644 --- a/examples/http_server/http_server_module.hpp +++ b/examples/http_server/http_server_module.hpp @@ -20,20 +20,12 @@ #pragma once -#include "ark/http/http_server_module.hpp" -#include "ark/plugin_manager.hpp" +#include "ark/module.hpp" namespace ark { -class http_server_module final : public module +class http_server_sample_module : public module { - ARK_DECLARE_MODULE_FUNCTIONS -public: - bool init() override; - bool post_init() override; - -protected: - http_server_module* http_server_module_; }; } // namespace ark diff --git a/examples/http_server/http_server_plugin.cpp b/examples/http_server/http_server_plugin.cpp index 688b8ea86..439a70f6e 100644 --- a/examples/http_server/http_server_plugin.cpp +++ b/examples/http_server/http_server_plugin.cpp @@ -18,19 +18,33 @@ * */ -#include "http_server_module.hpp" +#include + #include "http_server_plugin.hpp" namespace ark { -ARK_DECLARE_PLUGIN_DLL_FUNCTION(http_server_plugin) +ARK_EXPORT_FUNC void DllEntryPlugin(plugin_manager* manager, const std::string& plugin_name, logger* log) { + logger::init(log); + manager->register_plugin(plugin_name); +} + +ARK_EXPORT_FUNC void DllExitPlugin(plugin_manager* manager) { + manager->unregister_plugin(); +} void http_server_plugin::install() { - ARK_REGISTER_MODULE(http_server_module, http_server_module); + if (!register_module(make_http_server_sample_module(), module_update::disabled)) + { + throw std::runtime_error("http_server example module registration rejected"); + } } void http_server_plugin::uninstall() { - ARK_DEREGISTER_MODULE(http_server_module, http_server_module); + if (!unregister_module()) + { + throw std::runtime_error("http_server example module unregistration rejected"); + } } } // namespace ark diff --git a/examples/http_server/http_server_plugin.hpp b/examples/http_server/http_server_plugin.hpp index 403af9758..019f2ee71 100644 --- a/examples/http_server/http_server_plugin.hpp +++ b/examples/http_server/http_server_plugin.hpp @@ -20,12 +20,20 @@ #pragma once -#include "ark/module.hpp" -#include "ark/plugin.hpp" -#include "ark/plugin_manager.hpp" +#include + +#include "ark/plugin_base.hpp" +#include "http_server_module.hpp" namespace ark { -ARK_DECLARE_PLUGIN(http_server_plugin) +std::unique_ptr make_http_server_sample_module(); + +class http_server_plugin final : public plugin_base +{ +public: + void install() override; + void uninstall() override; +}; } // namespace ark diff --git a/tests/app/http_examples.cmake b/tests/app/http_examples.cmake new file mode 100644 index 000000000..e19915812 --- /dev/null +++ b/tests/app/http_examples.cmake @@ -0,0 +1,9 @@ +# Copyright (c) 2026 OpenArkStudio. SPDX-License-Identifier: Apache-2.0 +file(MAKE_DIRECTORY "${RUNTIME_DIR}") +execute_process(COMMAND "${PROGRAM}" "${PLUGIN_DIR}" WORKING_DIRECTORY "${RUNTIME_DIR}" + RESULT_VARIABLE result OUTPUT_VARIABLE output ERROR_VARIABLE errors TIMEOUT 20) +file(WRITE "${RUNTIME_DIR}/output.log" "${output}\n${errors}") +if(NOT "${result}" STREQUAL "0") + message(FATAL_ERROR "HTTP example DSO regression failed: ${result}\n${output}\n${errors}") +endif() +message(STATUS "${output}") diff --git a/tests/app/http_examples.cpp b/tests/app/http_examples.cpp new file mode 100644 index 000000000..7e9eb04d9 --- /dev/null +++ b/tests/app/http_examples.cpp @@ -0,0 +1,292 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "../../examples/http_client/http_client_sample_plugin.hpp" +#include "../../examples/http_server/http_server_plugin.hpp" + +namespace { +void require(bool condition, const std::string& message) { + if (!condition) + { + std::cerr << message << std::endl; + std::_Exit(1); + } +} + +struct observations +{ + unsigned future_posts{}; + unsigned callback_posts{}; + unsigned callbacks{}; + unsigned grants{}; + unsigned clients_destroyed{}; + unsigned updates{}; + unsigned starts{}; + unsigned pre_stops{}; + unsigned stops{}; + std::vector> pending; + std::thread::id owner = std::this_thread::get_id(); +}; + +class client_service final : public ark::module_base +{ +public: + explicit client_service(observations& events) + : events_(events) { + } + + std::future> async_request(arknet::http::verb, const std::string&, uint16_t, + const std::string&, const std::map*, const std::vector*, + const std::string*, const std::string*, int, int) override { + require(false, "Example unexpectedly selected async_request"); + return {}; + } + + std::future> get(const std::string&, uint16_t, const std::string&, + const std::map*, const std::vector*, int, int) override { + require(false, "Example unexpectedly selected GET"); + return {}; + } + + std::future> post(const std::string& host, uint16_t port, const std::string& path, + const std::map* params, const std::string* body, const std::string* content_type, + const std::vector* cookies, int io_timeout, int connect_timeout) override { + check_request(host, port, path, params, body, content_type, cookies, io_timeout, connect_timeout); + ++events_.future_posts; + std::promise> result; + auto future = result.get_future(); + result.set_value({true, "future-response"}); + return future; + } + + void post(std::function)> callback, const std::string& host, uint16_t port, + const std::string& path, const std::map* params, const std::string* body, + const std::string* content_type, const std::vector* cookies, int io_timeout, + int connect_timeout) override { + check_request(host, port, path, params, body, content_type, cookies, io_timeout, connect_timeout); + ++events_.callback_posts; + events_.pending.push_back([this, callback = std::move(callback)] { + require(std::this_thread::get_id() == events_.owner, "HTTP callback left the manager thread"); + callback({true, "callback-response"}); + ++events_.callbacks; + }); + } + + void post(std::function)>, const std::string&, const std::string&, + const std::string&, const std::map*, const std::string*, const std::string*, + const std::vector*, int, int) override { + require(false, "Example unexpectedly selected the service-name overload"); + } + + bool update() override { + ++events_.updates; + auto pending = std::move(events_.pending); + events_.pending.clear(); + for (auto& callback : pending) + { + callback(); + } + return true; + } + + bool pre_shut() override { + ++events_.pre_stops; + return true; + } + + bool shut() override { + ++events_.stops; + return true; + } + +private: + static void check_request(const std::string& host, uint16_t port, const std::string& path, + const std::map* params, const std::string* body, const std::string* content_type, + const std::vector* cookies, int io_timeout, int connect_timeout) { + require(host == "172.20.3.74" && port == 2379 && path == "/v3/kv/put" && params == nullptr && body && + *body == "{\"key\": \"Zm9v\", \"value\": \"YmFy\"}" && content_type == nullptr && + cookies == nullptr && io_timeout == 20 && connect_timeout == 5, + "Example POST payload, endpoint or timeout defaults changed"); + } + + observations& events_; +}; + +class lease_client final : public ark::etcd_client +{ +public: + explicit lease_client(observations& events) + : events_(events) { + } + + ~lease_client() override { + ++events_.clients_destroyed; + } + + void grant(std::function)> callback, int ttl) override { + require(ttl == 100, "Example lease TTL changed"); + ++events_.grants; + events_.pending.push_back([this, callback = std::move(callback)] { + require(std::this_thread::get_id() == events_.owner, "Lease callback left the manager thread"); + callback({42, ""}); + ++events_.callbacks; + }); + } + + void keepalive(std::function)>, int64_t) override { + require(false, "Example unexpectedly requested keepalive"); + } + +private: + observations& events_; +}; + +class etcd_service final : public ark::module_base +{ +public: + explicit etcd_service(observations& events) + : events_(events) { + } + + std::shared_ptr create_etcd_client( + std::vector endpoints, int io_timeout, int connect_timeout) override { + require(endpoints == std::vector{"172.20.3.74:2379"} && io_timeout == 5 && connect_timeout == 2, + "Example etcd endpoints or timeout defaults changed"); + return std::make_shared(events_); + } + +private: + observations& events_; +}; + +class server_service final : public ark::module_base +{ +public: + explicit server_service(observations& events) + : events_(events) { + } + + arknet::http_router& get_router() override { + return router_; + } + + bool start_server() override { + require(false, "Example unexpectedly selected configured start"); + return false; + } + + bool start_server(const std::string& host, uint16_t port, uint8_t threads) override { + require(host == "0.0.0.0" && port == 8080 && threads == 4, "Example listener arguments changed"); + ++events_.starts; + // The historical sample ignores this return value in post_init(). + return false; + } + +private: + observations& events_; + arknet::http_router router_; +}; + +class services final : public ark::plugin_base +{ +public: + explicit services(observations& events) + : events_(events) { + } + + void install() override { + require(register_module( + std::make_unique(events_), ark::module_update::enabled), + "HTTP fixture registration failed"); + require(register_module( + std::make_unique(events_), ark::module_update::disabled), + "Server fixture registration failed"); + require(register_module( + std::make_unique(events_), ark::module_update::disabled), + "Etcd fixture registration failed"); + } + + void uninstall() override { + require(unregister_module(), "Etcd fixture unregistration failed"); + require(unregister_module(), "Server fixture unregistration failed"); + require(unregister_module(), "HTTP fixture unregistration failed"); + } + +private: + observations& events_; +}; +} // namespace + +int main(int argc, char* argv[]) { + require(argc == 2, "Expected example plugin directory"); + auto client_factory = ark::make_http_client_sample_module(); + auto server_factory = ark::make_http_server_sample_module(); + auto other_client = ark::make_http_client_sample_module(); + auto other_server = ark::make_http_server_sample_module(); + require(client_factory && server_factory && other_client && other_server && + client_factory.get() != other_client.get() && server_factory.get() != other_server.get() && + client_factory->get_plugin_manager() == nullptr && server_factory->get_plugin_manager() == nullptr && + client_factory->get_name().empty() && server_factory->get_name().empty(), + "Private factories must return independent, unregistered interface owners"); + ark::logger log; + ark::logger::init(&log); + log.set_log_func([](spdlog::level::level_enum, const std::string&) {}); + for (int run = 0; run < 2; ++run) + { + observations events; + ark::plugin_manager manager; + services fixtures(events); + fixtures.set_plugin_manager(&manager); + fixtures.install(); + std::ostringstream output; + auto* original_output = std::cout.rdbuf(output.rdbuf()); + require(manager.start(0, std::string(argv[1]) + "/", "", + {{"http_client_sample_plugin", "client.toml"}, {"http_server_plugin", "server.toml"}}), + "Actual HTTP example DSO startup failed: " + manager.get_startup_failure()); + auto* client = manager.find_module(); + auto* server = manager.find_module(); + require(client && server && client->get_plugin_manager() == &manager && + server->get_plugin_manager() == &manager && + client->get_name() == typeid(ark::http_client_sample_module).name() && + server->get_name() == typeid(ark::http_server_sample_module).name(), + "Actual example DSO factory registration lost interface identity/ownership"); + require(manager.get_plugin_conf("http_client_sample_plugin") == "client.toml" && + manager.get_plugin_conf("http_server_plugin") == "server.toml", + "Example plugin configuration identity changed"); + require(events.future_posts == 1 && events.callback_posts == 1 && events.grants == 1 && events.starts == 1 && + events.callbacks == 0 && events.updates == 0, + "Example request flow changed or callbacks ran before manager update"); + require(output.str().find("Start testing Http Client") < output.str().find("future-response") && + output.str().find("future-response") < output.str().find("Finish testing Http Client") && + output.str().find("callback-response") == std::string::npos, + "Example must consume its future before startup completes and defer its callback output"); + require(manager.update(1) && manager.update(2) && events.callbacks == 2 && events.updates == 2, + "Example callbacks must complete exactly once through owner updates"); + require(output.str().find("callback-response") != std::string::npos && + output.str().find("42 |") != std::string::npos, + "Example callback/lease output changed"); + require(manager.stop(3) && manager.stop(4), "Actual example DSO normal/repeated stop failed"); + require(events.clients_destroyed == 1 && events.pre_stops == 1 && events.stops == 1, + "Actual example DSO stop retained lease ownership or repeated service shutdown"); + fixtures.uninstall(); + std::cout.rdbuf(original_output); + std::cout << output.str(); + } + std::cout << "HTTP examples: actual factories/DSOs, interface identity, requests/default timeouts, " + "owner-thread callbacks, listener arguments, lease destruction, repeated stop/reload passed\n"; +} diff --git a/tests/ci/http-examples.test.mjs b/tests/ci/http-examples.test.mjs new file mode 100644 index 000000000..05fe240e2 --- /dev/null +++ b/tests/ci/http-examples.test.mjs @@ -0,0 +1,69 @@ +// Copyright (c) 2026 OpenArkStudio. SPDX-License-Identifier: Apache-2.0 +import assert from 'node:assert/strict'; +import { execFileSync } from 'node:child_process'; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import test from 'node:test'; +const read = path => readFileSync(new URL(`../../${path}`, import.meta.url), 'utf8'); + +test('HTTP example regressions build the real DSO sources without enabling application examples', () => { + const cmake = read('tests/core/CMakeLists.txt'); + assert.match(cmake, /option\(ARK_BUILD_HTTP_EXAMPLE_TESTS "[^"]+" \$\{ark_http_examples_default\}\)/); + const block = cmake.slice(cmake.indexOf('if(ARK_BUILD_HTTP_EXAMPLE_TESTS)')); + assert.match(block, /add_subdirectory\("\$\{PROJECT_SOURCE_DIR\}\/examples\/http_client"/); + assert.match(block, /add_subdirectory\("\$\{PROJECT_SOURCE_DIR\}\/examples\/http_server"/); + assert.match(block, /DEPENDS ark_http_examples_tests http_client_sample_plugin http_server_plugin/); + assert.match(block, /add_dependencies\(check-core check-http-examples\)/); + assert.match(read('examples/CMakeLists.txt'), /^#add_subdirectory\(http_client\)$/m); + assert.match(read('examples/CMakeLists.txt'), /^#add_subdirectory\(http_server\)$/m); + for (const [folder, stem] of [['http_client', 'http_client_sample'], ['http_server', 'http_server']]) { + assert.match(read(`examples/${folder}/CMakeLists.txt`), + new RegExp(`add_library\\(${stem}_plugin SHARED ${stem}_module\\.cpp ${stem}_plugin\\.cpp\\)`)); + } +}); + + +test('HTTP example test default follows core Boost prerequisites and honors an explicit OFF', () => { + const cmake = read('tests/core/CMakeLists.txt'); + const defaults = cmake.slice(cmake.indexOf('set(ark_http_examples_default OFF)'), + cmake.indexOf('if(ARK_BUILD_HTTP_EXAMPLE_TESTS)')); + const fixture = mkdtempSync(join(tmpdir(), 'ark-http-examples-default-')); + try { + for (const [index, standalone, missing, explicitOff, expected] of [ + [0, false, '', false, 'ON'], + [1, false, '', true, 'OFF'], + [2, true, '', false, 'OFF'], + [3, false, 'http', false, 'OFF'], + [4, false, 'net', false, 'OFF'], + [5, false, 'etcd_client', false, 'OFF'], + ]) { + writeFileSync(join(fixture, 'CMakeLists.txt'), `cmake_minimum_required(VERSION 3.21) +project(http_examples_default LANGUAGES NONE) +set(ASIO_STANDALONE ${standalone ? 'ON' : 'OFF'}) +${['http', 'net', 'etcd_client'].filter(name => name !== missing) + .map(name => `add_library(ark::${name} INTERFACE IMPORTED)`).join('\n')} +${defaults} +if(NOT ARK_BUILD_HTTP_EXAMPLE_TESTS STREQUAL "${expected}") + message(FATAL_ERROR "Unexpected HTTP example test selection") +endif() +`); + execFileSync('cmake', ['-S', fixture, '-B', join(fixture, `build-${index}`), + ...(explicitOff ? ['-DARK_BUILD_HTTP_EXAMPLE_TESTS=OFF'] : [])], { stdio: 'pipe' }); + } + } finally { + rmSync(fixture, { recursive: true, force: true }); + } +}); + +test('existing Applications CI reaches HTTP example regressions through core Boost validation', () => { + const workflow = read('.github/workflows/ci.yml').replaceAll('\r\n', '\n'); + const applications = workflow.split(/(?=^ [a-z][a-z0-9_-]*:$)/m) + .find(block => block.startsWith(' applications:\n')); + assert.ok(applications); + const configure = applications.match(/ - name: Configure applications[\s\S]*?(?=\n - )/)?.[0]; + assert.match(configure ?? '', /-DARK_BUILD_CORE_TESTS=ON/); + assert.match(configure ?? '', /-DASIO_STANDALONE=OFF/); + assert.doesNotMatch(configure ?? '', /-DARK_BUILD_HTTP_EXAMPLE_TESTS=OFF/); + assert.match(applications, /run: cmake --build ci-build --target check-core/); +}); diff --git a/tests/ci/plugin-names.test.mjs b/tests/ci/plugin-names.test.mjs index e17bce819..6e99c3c57 100644 --- a/tests/ci/plugin-names.test.mjs +++ b/tests/ci/plugin-names.test.mjs @@ -42,7 +42,9 @@ test('capability examples preserve the enabled set and retain disabled examples for (const [index, name] of names.entries()) { assert.match(cmake, new RegExp(`^${index < 3 ? '' : '#'}add_subdirectory\\(${name}\\)$`, 'm')); const header = readFileSync(resolve(root, `examples/${name}/${name}_module.hpp`), 'utf8'); - assert.match(header, new RegExp(`class ${name}_module final`)); + assert.match(header, name === 'http_server' + ? /class http_server_sample_module : public module/ + : new RegExp(`class ${name}_module final`)); const manifest = readFileSync(resolve(root, `config/${name}.app.toml`), 'utf8'); assert.ok(manifest.includes(`name = "${name}_plugin"`)); } diff --git a/tests/core/CMakeLists.txt b/tests/core/CMakeLists.txt index 0912ed27c..4da71bd60 100644 --- a/tests/core/CMakeLists.txt +++ b/tests/core/CMakeLists.txt @@ -274,3 +274,29 @@ if(TARGET ark_http_plugin) USES_TERMINAL VERBATIM) add_dependencies(check-core check-http-plugin) endif() + +# Legacy HTTP demonstrations remain excluded from normal applications/examples. +# Core validation uses the real sources/DSOs when their public prerequisites exist. +set(ark_http_examples_default OFF) +if(NOT ASIO_STANDALONE AND TARGET ark::http AND TARGET ark::net AND TARGET ark::etcd_client) + set(ark_http_examples_default ON) +endif() +option(ARK_BUILD_HTTP_EXAMPLE_TESTS "Build HTTP example construction regressions" ${ark_http_examples_default}) +if(ARK_BUILD_HTTP_EXAMPLE_TESTS) + if(ASIO_STANDALONE OR NOT TARGET ark::http OR NOT TARGET ark::net OR NOT TARGET ark::etcd_client) + message(FATAL_ERROR "HTTP example tests require Boost.Asio/Beast and ark::http, ark::net, ark::etcd_client") + endif() + add_subdirectory("${PROJECT_SOURCE_DIR}/examples/http_client" "${CMAKE_CURRENT_BINARY_DIR}/http_client_example") + add_subdirectory("${PROJECT_SOURCE_DIR}/examples/http_server" "${CMAKE_CURRENT_BINARY_DIR}/http_server_example") + add_executable(ark_http_examples_tests ../app/http_examples.cpp + ../../examples/http_client/http_client_sample_module.cpp + ../../examples/http_server/http_server_module.cpp) + set_property(TARGET ark_http_examples_tests PROPERTY LINK_LIBRARIES ark::http ark::net ark::etcd_client) + add_custom_target(check-http-examples + COMMAND ${CMAKE_COMMAND} "-DPROGRAM=$" + "-DPLUGIN_DIR=$" + "-DRUNTIME_DIR=${CMAKE_CURRENT_BINARY_DIR}/http examples runtime" + -P "${PROJECT_SOURCE_DIR}/tests/app/http_examples.cmake" + DEPENDS ark_http_examples_tests http_client_sample_plugin http_server_plugin USES_TERMINAL) + add_dependencies(check-core check-http-examples) +endif() diff --git a/tests/core/README.md b/tests/core/README.md index 88ef7a7de..830198946 100644 --- a/tests/core/README.md +++ b/tests/core/README.md @@ -98,3 +98,15 @@ With `ARK_BUILD_PLUGIN_HTTP=ON`, `check-http-plugin` loads the actual HTTP DSO a Applications CI explicitly enables `ARK_BUILD_PLUGIN_HTTP=ON` and runs `check-core`, so its existing source and full matrices include the real HTTP DSO fixture. Local HTTP-enabled native validation has passed; hosted full-matrix acceptance for this change remains pending. The permanent CI policy test guards this configuration-to-DSO dependency chain. Kernel maps its 38 existing headers from `src/plugins/kernel/` through `ark::kernel`. `check-core` retains data/node regressions; `check-kernel-initialization` retains initialization/configuration-copy assertions. The independent `tests/consumer/kernel` project consumes 38/35 headers with Protobuf enabled/disabled; `container_meta.hpp` retains its existing `ark/define.hpp` and `ark/enum.hpp` include prerequisites. See [Kernel reference](../../docs/reference/kernel.md). + +`ARK_BUILD_HTTP_EXAMPLE_TESTS` defaults to `ON` in core-test configurations with +Boost.Asio and all three API targets (`ark::http`, `ark::net`, `ark::etcd_client`), +and to `OFF` otherwise. Explicit `OFF` is respected. When selected, it adds +`check-http-examples` to `check-core`, compiling the actual legacy HTTP example +modules and their shared-library entries. Existing Applications CI exercises this +dependency; the default application-example selection remains unchanged. +The fixture checks interface factories/RTTI/configuration, original POST/lease +arguments and timeout defaults, owner-update callback delivery, listener arguments, +lease destruction and repeated stop/reload using isolated public service doubles. +No external HTTP/etcd request is made. See [HTTP examples](../../docs/reference/http.md#legacy-http-example-construction) +for the preserved fixed demonstration endpoints and the real-wire coverage boundary. diff --git a/tests/core/README_CN.md b/tests/core/README_CN.md index cb312c638..8da29e0bc 100644 --- a/tests/core/README_CN.md +++ b/tests/core/README_CN.md @@ -87,3 +87,11 @@ cmake --build core-build --target check-business-runtime Applications CI 显式开启 `ARK_BUILD_PLUGIN_HTTP=ON` 并运行 `check-core`,因此既有源码检查与全矩阵均包含真实 HTTP 动态库回归。本地启用 HTTP 的原生验证已通过;本次变更的托管全矩阵仍待验收。永久 CI 策略测试检查配置到真实动态库回归的依赖链。 Kernel 的 38 个既有头通过 `ark::kernel` 从 `src/plugins/kernel/` 映射;`check-core` 保留数据与节点回归,`check-kernel-initialization` 保留初始化与配置复制断言。独立工程 `tests/consumer/kernel` 在 Protobuf 开启/关闭时分别消费 38/35 个头,其中 `container_meta.hpp` 保留原有 `ark/define.hpp` 与 `ark/enum.hpp` 前置包含要求。详见 [Kernel 参考](../../docs/reference/kernel_CN.md)。 + +核心测试配置使用 Boost.Asio 且存在 `ark::http`、`ark::net`、`ark::etcd_client` 三个 API target 时, +`ARK_BUILD_HTTP_EXAMPLE_TESTS` 默认 `ON`,否则默认 `OFF`;显式设置 `OFF` 仍然有效。 +选中后 `check-core` 包含 `check-http-examples`,编译真实旧 HTTP 示例模块及动态库入口。 +既有 Applications CI 通过该依赖运行测试,默认应用示例集合不变。 +测试通过独立的公开服务替身验证接口工厂/RTTI/配置、原有 POST/租约参数与超时默认值、 +所属线程 update 回调派发、监听参数、租约析构及重复停机/重新加载,不向外部 HTTP/etcd 发请求。 +保留的固定演示端点与真实传输验证边界见 [HTTP 示例](../../docs/reference/http_CN.md#旧-http-示例的类型化创建)。 From cd59d3a1fc95b6d271445103d2430ab99b3a9c5e Mon Sep 17 00:00:00 2001 From: NickYang <3690738+nick2781@users.noreply.github.com> Date: Thu, 22 Oct 2026 16:01:33 +0000 Subject: [PATCH 2/2] refactor(server)!: colocate typed plugin construction (#144) Co-locate each server role's headers and cpp files, and replace creation macros with the existing module/plugin bases, private typed factories and explicit dynamic-library entry points. All nine modules retain disabled updates and existing business/lifecycle bodies. Build-only forwarding preserves 28 legacy source include paths; public installed headers are unchanged. Seven enabled server libraries are covered. Simulate remains disabled and is not claimed as natively accepted. English/Chinese directory and architecture documentation describe the resulting layout and verification boundary. Validation on macOS with the arknet 0.2.3 release tree and Boost/TLS: - All seven actual DSOs load, export both entry points and unload in isolated test processes. - Actual factory/assembly tests preserve interface keys, disabled updates, duplicate rejection and reinstall; counted rejection tests verify destruction, and a controlled Login module verifies the real manager entry/exit path. - Full `check-core` passes, including 59 core cases / 2,882 assertions and the existing application, Bus, Net and newly accepted Kernel DSO regressions. - 130 policy tests, clang-format 18 and bilingual Nimbus checks pass: 135 pages and 265 internal references. The DSO export probes run separately from hosts compiling the real implementations, preventing duplicate static Protobuf descriptor registration. These checks do not exercise full business initialization or establish deployment capacity. Hosted cross-platform merge-candidate checks remain required; the draft waits behind older admitted PRs. BREAKING CHANGE: concrete server plugin/module inheritance layouts change. Rebuild consumers and runtime libraries together; the 28 legacy source header paths remain available. --- docs/concepts/server-architecture.md | 20 ++++ docs/concepts/server-architecture_CN.md | 20 ++++ docs/reference/directory-structure.md | 9 ++ docs/reference/directory-structure_CN.md | 9 ++ src/server/CMakeLists.txt | 15 ++- src/server/compat_headers.cmake | 45 ++++++++ src/server/dir/dir_module_factory.hpp | 12 ++ .../dir/{interface => }/dir_net_module.hpp | 0 .../dir/{src => }/dir_net_module_impl.cpp | 7 +- .../dir/{include => }/dir_net_module_impl.hpp | 7 +- src/server/dir/{src => }/dir_plugin.cpp | 25 ++++- src/server/dir/{include => }/dir_plugin.hpp | 11 +- src/server/game/game_module_factory.hpp | 12 ++ .../game/{interface => }/game_net_module.hpp | 0 .../game/{src => }/game_net_module_impl.cpp | 7 +- .../{include => }/game_net_module_impl.hpp | 7 +- src/server/game/{src => }/game_plugin.cpp | 25 ++++- src/server/game/{include => }/game_plugin.hpp | 11 +- src/server/login/login_module_factory.hpp | 12 ++ .../{interface => }/login_net_module.hpp | 0 .../login/{src => }/login_net_module_impl.cpp | 7 +- .../{include => }/login_net_module_impl.hpp | 7 +- src/server/login/{src => }/login_plugin.cpp | 25 ++++- .../login/{include => }/login_plugin.hpp | 11 +- src/server/master/master_module_factory.hpp | 12 ++ .../{interface => }/master_net_module.hpp | 0 .../{src => }/master_net_module_impl.cpp | 7 +- .../{include => }/master_net_module_impl.hpp | 7 +- src/server/master/{src => }/master_plugin.cpp | 25 ++++- .../master/{include => }/master_plugin.hpp | 10 +- src/server/proxy/proxy_module_factory.hpp | 12 ++ .../{interface => }/proxy_net_module.hpp | 0 .../proxy/{src => }/proxy_net_module_impl.cpp | 7 +- .../{include => }/proxy_net_module_impl.hpp | 7 +- src/server/proxy/{src => }/proxy_plugin.cpp | 25 ++++- .../proxy/{include => }/proxy_plugin.hpp | 10 +- src/server/router/router_module_factory.hpp | 12 ++ .../{interface => }/router_net_module.hpp | 0 .../{src => }/router_net_module_impl.cpp | 7 +- .../{include => }/router_net_module_impl.hpp | 7 +- src/server/router/{src => }/router_plugin.cpp | 25 ++++- .../router/{include => }/router_plugin.hpp | 10 +- .../{include => }/simulate_define.hpp | 0 .../{interface => }/simulate_module.hpp | 4 +- .../simulate/simulate_module_factory.hpp | 14 +++ .../{src => }/simulate_module_impl.cpp | 7 +- .../{include => }/simulate_module_impl.hpp | 7 +- .../{include => }/simulate_msg_define.hpp | 0 .../{interface => }/simulate_net_module.hpp | 0 .../{src => }/simulate_net_module_impl.cpp | 7 +- .../simulate_net_module_impl.hpp | 9 +- src/server/simulate/simulate_plugin.cpp | 59 ++++++++++ .../{include => }/simulate_plugin.hpp | 10 +- src/server/simulate/src/simulate_plugin.cpp | 41 ------- src/server/world/world_module_factory.hpp | 12 ++ .../{interface => }/world_net_module.hpp | 0 .../world/{src => }/world_net_module_impl.cpp | 7 +- .../{include => }/world_net_module_impl.hpp | 7 +- src/server/world/{src => }/world_plugin.cpp | 25 ++++- .../world/{include => }/world_plugin.hpp | 10 +- tests/ci/server-construction.test.mjs | 105 ++++++++++++++++++ tests/core/CMakeLists.txt | 4 + tests/server/CMakeLists.txt | 50 +++++++++ tests/server/construction.cpp | 71 ++++++++++++ tests/server/entry.cpp | 44 ++++++++ tests/server/entry_factory.cpp | 50 +++++++++ tests/server/exports.cpp | 31 ++++++ tests/server/registration.cpp | 86 ++++++++++++++ tests/server/types.hpp.in | 16 +++ 69 files changed, 1008 insertions(+), 155 deletions(-) create mode 100644 src/server/compat_headers.cmake create mode 100644 src/server/dir/dir_module_factory.hpp rename src/server/dir/{interface => }/dir_net_module.hpp (100%) rename src/server/dir/{src => }/dir_net_module_impl.cpp (89%) rename src/server/dir/{include => }/dir_net_module_impl.hpp (88%) rename src/server/dir/{src => }/dir_plugin.cpp (54%) rename src/server/dir/{include => }/dir_plugin.hpp (82%) create mode 100644 src/server/game/game_module_factory.hpp rename src/server/game/{interface => }/game_net_module.hpp (100%) rename src/server/game/{src => }/game_net_module_impl.cpp (99%) rename src/server/game/{include => }/game_net_module_impl.hpp (96%) rename src/server/game/{src => }/game_plugin.cpp (54%) rename src/server/game/{include => }/game_plugin.hpp (82%) create mode 100644 src/server/login/login_module_factory.hpp rename src/server/login/{interface => }/login_net_module.hpp (100%) rename src/server/login/{src => }/login_net_module_impl.cpp (92%) rename src/server/login/{include => }/login_net_module_impl.hpp (89%) rename src/server/login/{src => }/login_plugin.cpp (54%) rename src/server/login/{include => }/login_plugin.hpp (82%) create mode 100644 src/server/master/master_module_factory.hpp rename src/server/master/{interface => }/master_net_module.hpp (100%) rename src/server/master/{src => }/master_net_module_impl.cpp (94%) rename src/server/master/{include => }/master_net_module_impl.hpp (91%) rename src/server/master/{src => }/master_plugin.cpp (53%) rename src/server/master/{include => }/master_plugin.hpp (81%) create mode 100644 src/server/proxy/proxy_module_factory.hpp rename src/server/proxy/{interface => }/proxy_net_module.hpp (100%) rename src/server/proxy/{src => }/proxy_net_module_impl.cpp (95%) rename src/server/proxy/{include => }/proxy_net_module_impl.hpp (93%) rename src/server/proxy/{src => }/proxy_plugin.cpp (54%) rename src/server/proxy/{include => }/proxy_plugin.hpp (82%) create mode 100644 src/server/router/router_module_factory.hpp rename src/server/router/{interface => }/router_net_module.hpp (100%) rename src/server/router/{src => }/router_net_module_impl.cpp (92%) rename src/server/router/{include => }/router_net_module_impl.hpp (87%) rename src/server/router/{src => }/router_plugin.cpp (53%) rename src/server/router/{include => }/router_plugin.hpp (81%) rename src/server/simulate/{include => }/simulate_define.hpp (100%) rename src/server/simulate/{interface => }/simulate_module.hpp (96%) create mode 100644 src/server/simulate/simulate_module_factory.hpp rename src/server/simulate/{src => }/simulate_module_impl.cpp (98%) rename src/server/simulate/{include => }/simulate_module_impl.hpp (94%) rename src/server/simulate/{include => }/simulate_msg_define.hpp (100%) rename src/server/simulate/{interface => }/simulate_net_module.hpp (100%) rename src/server/simulate/{src => }/simulate_net_module_impl.cpp (98%) rename src/server/simulate/{include => }/simulate_net_module_impl.hpp (90%) create mode 100644 src/server/simulate/simulate_plugin.cpp rename src/server/simulate/{include => }/simulate_plugin.hpp (81%) delete mode 100644 src/server/simulate/src/simulate_plugin.cpp create mode 100644 src/server/world/world_module_factory.hpp rename src/server/world/{interface => }/world_net_module.hpp (100%) rename src/server/world/{src => }/world_net_module_impl.cpp (97%) rename src/server/world/{include => }/world_net_module_impl.hpp (93%) rename src/server/world/{src => }/world_plugin.cpp (54%) rename src/server/world/{include => }/world_plugin.hpp (82%) create mode 100644 tests/ci/server-construction.test.mjs create mode 100644 tests/server/CMakeLists.txt create mode 100644 tests/server/construction.cpp create mode 100644 tests/server/entry.cpp create mode 100644 tests/server/entry_factory.cpp create mode 100644 tests/server/exports.cpp create mode 100644 tests/server/registration.cpp create mode 100644 tests/server/types.hpp.in diff --git a/docs/concepts/server-architecture.md b/docs/concepts/server-architecture.md index 390d35fc4..089bd127e 100644 --- a/docs/concepts/server-architecture.md +++ b/docs/concepts/server-architecture.md @@ -34,3 +34,23 @@ Discovery is a separate integration decision. The framework does not require eve ## Limits to verify in a deployment Endpoint connectivity alone does not verify authentication, message delivery after reconnect, state transfer, split-brain behavior or storage consistency. The 100,000–200,000 concurrent-user target is a multi-process deployment goal, not a measured single-process limit. [Benchmarks and acceptance](../performance/benchmark.md) distinguish local correctness/performance tools from cross-host capacity and endurance tests. + +## Verify server construction + +The built-in server plugins use typed module factories and explicit dynamic-library entry points. Construction keeps the existing plugin version, configuration access, interface RTTI names, installation order and disabled frame updates. Network callbacks, lifecycle methods, protocols and resources retain their existing behavior. + +With the [application build dependencies](../networking.md) installed, run: + +```sh +cmake -S . -B out/server-construction -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" \ + '-DVCPKG_MANIFEST_FEATURES=application;boost;etcd-client' \ + -DCMAKE_BUILD_TYPE=Debug -DASIO_STANDALONE=OFF \ + -DARK_BUILD_APPLICATIONS=ON -DARK_BUILD_PROTO=ON \ + -DARK_BUILD_CORE_TESTS=ON -DARK_BUILD_EXAMPLES=OFF +cmake --build out/server-construction --target check-server-plugins --parallel 2 +``` + +`check-server-plugins` exercises the real factory and assembly of each of the seven enabled server libraries, checks the retained source include paths, verifies disabled update admission, and loads each actual shared library to inspect both exported entry symbols. Each role runs a minimal core-only export host before its separate real-factory host. This process separation prevents the host and shared library from registering the same statically linked AFProto descriptors twice. A counted substitute through the real Login assembly checks destruction of rejected candidates, preservation of the original registration and a successful retry. `check-core` includes these focused checks in application builds. A separate shared-library fixture runs the real Login entry and exit through the manager loader with a controlled module. The seven business libraries receive export inspection and direct construction checks, without business lifecycle calls. These checks do not start network services or establish deployment readiness. + +`node --test tests/ci/server-construction.test.mjs` checks configure-only compatibility forwarding and construction policy without compiling native code. Simulate has source-policy coverage only; its unsupported PhysX 3/MessagePack integration is outside these native targets. diff --git a/docs/concepts/server-architecture_CN.md b/docs/concepts/server-architecture_CN.md index 2f39742f9..03b830941 100644 --- a/docs/concepts/server-architecture_CN.md +++ b/docs/concepts/server-architecture_CN.md @@ -34,3 +34,23 @@ Bus 插件表示进程地址与路由目标。[Bus 参考](../reference/bus_CN.m ## 部署时需要验证的边界 端点可连通,不等于已经验证鉴权、重连后的消息交付、状态转移、网络分区行为或存储一致性。10 万至 20 万同时在线是多进程部署目标,不是实测的单进程上限。[基准测试与验收](../performance/benchmark_CN.md)区分本机正确性与性能工具,以及跨主机容量和耐久测试。 + +## 验证服务端创建方式 + +内置服务端插件使用类型化模块工厂与显式动态库入口。改造保留插件版本、配置访问、接口 RTTI 注册名、安装顺序与关闭帧更新的行为;网络回调、生命周期方法、协议与资源行为保持不变。 + +安装[应用构建依赖](../networking_CN.md)后运行: + +```sh +cmake -S . -B out/server-construction -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" \ + '-DVCPKG_MANIFEST_FEATURES=application;boost;etcd-client' \ + -DCMAKE_BUILD_TYPE=Debug -DASIO_STANDALONE=OFF \ + -DARK_BUILD_APPLICATIONS=ON -DARK_BUILD_PROTO=ON \ + -DARK_BUILD_CORE_TESTS=ON -DARK_BUILD_EXAMPLES=OFF +cmake --build out/server-construction --target check-server-plugins --parallel 2 +``` + +`check-server-plugins` 对七个启用的服务动态库分别检查真实工厂与装配、原有源码包含路径和关闭更新的注册方式,并加载实际动态库检查两个导出入口符号。每个角色先在仅链接核心的最小进程中检查导出,再在独立进程中检查真实工厂,避免宿主与动态库在同一进程内重复注册静态链接的 AFProto 描述符。真实 Login 装配另以计数替身检查被拒绝候选对象的析构、原有注册的保留以及重试成功。应用构建中的 `check-core` 包含这些定向检查。另一个动态库夹具通过管理器加载器运行真实 Login 入口与退出函数,并使用受控模块。七个业务动态库只检查导出与直接创建,不调用业务生命周期。这些检查不启动网络服务,也不代表部署就绪。 + +`node --test tests/ci/server-construction.test.mjs` 在不编译原生代码的情况下检查兼容转发映射与创建约束。Simulate 只参与源码策略检查,其未受支持的 PhysX 3/MessagePack 集成不在这些原生目标中。 diff --git a/docs/reference/directory-structure.md b/docs/reference/directory-structure.md index 9db75262f..bbb33359d 100644 --- a/docs/reference/directory-structure.md +++ b/docs/reference/directory-structure.md @@ -154,6 +154,15 @@ cmake --build etcd-client-headers --target check-etcd-client-headers --parallel Header mapping does not establish Consul, Kafka or OSS SDK runtime compatibility. Physic and `physics_scene` still require the unsupported legacy PhysX 3 integration and retain their explicit configuration failure; no passing PhysX build is claimed. + +## Built-in server source layout + +The eight server source families co-locate their interface, implementation and plugin hpp/cpp files in `src/server//`. Each implementation derives from `module_base`; private factories supply interface-owned `std::unique_ptr` values to `plugin_base`. The nine existing modules retain interface RTTI registration names and disabled frame updates. Each library defines its `DllEntryPlugin` and `DllExitPlugin` functions explicitly. + +Server source includes such as `login/interface/login_net_module.hpp` and `login/include/login_plugin.hpp` remain available through build-tree forwarding headers under `src/server/compat-include/`. Internal source uses adjacent headers. The forwarding inventory preserves all 28 previous headers, including implementation and plugin declarations, without adding the private factory headers to that inventory. This compatibility mapping is separate from public `` mappings and is not a new installed server API. Rebuild C++ consumers after changing the concrete inheritance chain. + +With `ARK_BUILD_APPLICATIONS=ON`, CMake builds seven server libraries: Game, Login, Proxy, World, Master, Dir and Router. The Simulate sources use the same construction pattern, but their target remains disabled and its explicit unsupported PhysX 3/MessagePack configuration failure is retained. Source colocation does not validate that integration. See [server architecture](../concepts/server-architecture.md#verify-server-construction) for the focused construction checks and their limits. + ## Installed layout [Installation](../get-started/installation.md#install-and-consume-the-cmake-package) preserves logical `` headers under `/include/ark/`. CMake package files use the GNUInstallDirs library directory under `cmake/ark/`; selected launcher and runtime plugins install under `bin/`. Configuration samples and licensing are under `share/ark/`. Consumers use `find_package(ark CONFIG REQUIRED)` and `ark::core` / `ark::` targets; source consumers use `add_subdirectory` with the same target names. Neither requires a generated mapping path. Source/build directories are absent from installed exports; arknet and other dependencies remain external. `tests/install/` contains independent installed consumers and the disposable-producer relocation test. diff --git a/docs/reference/directory-structure_CN.md b/docs/reference/directory-structure_CN.md index 109ffa2cc..d83bc7044 100644 --- a/docs/reference/directory-structure_CN.md +++ b/docs/reference/directory-structure_CN.md @@ -147,3 +147,12 @@ cmake --build etcd-client-headers --target check-etcd-client-headers --parallel ## 安装目录 [安装](../get-started/installation_CN.md#安装并消费-cmake-包)后,逻辑 `` 头位于 `/include/ark/`。CMake 包文件位于 GNUInstallDirs 库目录下的 `cmake/ark/`,选定启动程序与运行时插件位于 `bin/`,配置样例与许可证位于 `share/ark/`。安装消费者使用 `find_package(ark CONFIG REQUIRED)` 和 `ark::core`/`ark::`;源码消费者使用 `add_subdirectory` 和相同 target,无需指定生成映射路径。安装导出不包含源码或构建目录;arknet 等依赖仍由外部提供。`tests/install/` 保存独立安装消费者与删除临时生产者后的迁移检查。 + + +## 内置服务端源码布局 + +八组服务端源码将接口、实现与插件的 hpp/cpp 共置于 `src/server//`。实现类继承 `module_base`,私有工厂向 `plugin_base` 提供由接口类型持有的 `std::unique_ptr`。原有九个模块保留接口 RTTI 注册名,均不启用帧更新。每个动态库显式定义 `DllEntryPlugin` 与 `DllExitPlugin`。 + +`login/interface/login_net_module.hpp`、`login/include/login_plugin.hpp` 等源码包含路径通过构建树中的 `src/server/compat-include/` 转发头保留,内部源码使用相邻头文件。转发清单保留原有全部 28 个头文件,包括实现与插件声明,但不包含新增的私有工厂头。该兼容映射独立于公开 `` 映射,不新增可安装的服务端 API。具体类的继承链变化后,C++ 消费方需要重新编译。 + +设置 `ARK_BUILD_APPLICATIONS=ON` 后,CMake 编译 Game、Login、Proxy、World、Master、Dir、Router 七个服务动态库。Simulate 源码采用相同创建方式,但仍不参与默认构建,并保留未受支持的 PhysX 3/MessagePack 集成配置失败。源码共置不代表该集成已经通过验证。定向创建检查及其边界见[服务器架构](../concepts/server-architecture_CN.md#验证服务端创建方式)。 diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index 065e38eb2..630765433 100755 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -1,3 +1,5 @@ +include(compat_headers.cmake) + function(BUILD_SERVER_MACRO project_name) ark_validate_plugin_name("${project_name}") if(UNIX AND (NOT APPLE)) @@ -6,25 +8,26 @@ function(BUILD_SERVER_MACRO project_name) set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) endif(UNIX AND (NOT APPLE)) - file(GLOB_RECURSE PLUGIN_SRC *.h *.hpp *.cpp *.cc *.c) + file(GLOB_RECURSE PLUGIN_SRC CONFIGURE_DEPENDS *.h *.hpp *.cpp *.cc *.c) add_library(${project_name} SHARED ${PLUGIN_SRC}) + target_link_libraries(${project_name} PRIVATE ark_server_compat_headers) add_dependencies(${project_name} AFProto) if(WIN32) add_definitions(-D_USRDLL) - target_link_libraries(${project_name} + target_link_libraries(${project_name} PRIVATE debug AFProto_d.lib optimized AFProto.lib ${PROTOBUF_LIBRARIES}) elseif(APPLE) - target_link_libraries(${project_name} + target_link_libraries(${project_name} PRIVATE debug ${BIN_OUTPUT_DIR}/AFProto_d.a optimized ${BIN_OUTPUT_DIR}/AFProto.a ${PROTOBUF_LIBRARIES}) else() - target_link_libraries(${project_name} + target_link_libraries(${project_name} PRIVATE debug :AFProto_d.a optimized :AFProto.a ${PROTOBUF_LIBRARIES}) @@ -57,3 +60,7 @@ add_subdirectory(master) add_subdirectory(dir) add_subdirectory(router) #add_subdirectory(simulate) + +if(ARK_BUILD_CORE_TESTS) + add_subdirectory("${PROJECT_SOURCE_DIR}/tests/server" "${CMAKE_CURRENT_BINARY_DIR}/tests") +endif() diff --git a/src/server/compat_headers.cmake b/src/server/compat_headers.cmake new file mode 100644 index 000000000..0e15becf3 --- /dev/null +++ b/src/server/compat_headers.cmake @@ -0,0 +1,45 @@ +# Retain source-consumer include paths after co-locating server headers. +# These build-tree forwarding headers are private to built-in server targets; +# public ark/... plugin mappings and installed headers are unchanged. +set(ark_server_legacy_headers + "game/include/game_net_module_impl.hpp" + "game/include/game_plugin.hpp" + "game/interface/game_net_module.hpp" + "login/include/login_net_module_impl.hpp" + "login/include/login_plugin.hpp" + "login/interface/login_net_module.hpp" + "proxy/include/proxy_net_module_impl.hpp" + "proxy/include/proxy_plugin.hpp" + "proxy/interface/proxy_net_module.hpp" + "world/include/world_net_module_impl.hpp" + "world/include/world_plugin.hpp" + "world/interface/world_net_module.hpp" + "master/include/master_net_module_impl.hpp" + "master/include/master_plugin.hpp" + "master/interface/master_net_module.hpp" + "dir/include/dir_net_module_impl.hpp" + "dir/include/dir_plugin.hpp" + "dir/interface/dir_net_module.hpp" + "router/include/router_net_module_impl.hpp" + "router/include/router_plugin.hpp" + "router/interface/router_net_module.hpp" + "simulate/include/simulate_define.hpp" + "simulate/include/simulate_module_impl.hpp" + "simulate/include/simulate_msg_define.hpp" + "simulate/include/simulate_net_module_impl.hpp" + "simulate/include/simulate_plugin.hpp" + "simulate/interface/simulate_module.hpp" + "simulate/interface/simulate_net_module.hpp" +) +set(ark_server_compat_include "${CMAKE_CURRENT_BINARY_DIR}/compat-include") +# Only this owned mapping directory is regenerated. +file(REMOVE_RECURSE "${ark_server_compat_include}") +foreach(header IN LISTS ark_server_legacy_headers) + string(REGEX REPLACE "^([^/]+)/[^/]+/(.+)$" "\\1/\\2" source "${header}") + get_filename_component(directory "${ark_server_compat_include}/${header}" DIRECTORY) + file(MAKE_DIRECTORY "${directory}") + file(WRITE "${ark_server_compat_include}/${header}" + "// Generated compatibility forwarding header. Edit the source header.\n#pragma once\n#include \"${CMAKE_CURRENT_LIST_DIR}/${source}\"\n") +endforeach() +add_library(ark_server_compat_headers INTERFACE) +target_include_directories(ark_server_compat_headers INTERFACE "${ark_server_compat_include}") diff --git a/src/server/dir/dir_module_factory.hpp b/src/server/dir/dir_module_factory.hpp new file mode 100644 index 000000000..1c3cd6c6b --- /dev/null +++ b/src/server/dir/dir_module_factory.hpp @@ -0,0 +1,12 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#pragma once + +#include + +#include "dir_net_module.hpp" + +namespace ark::detail { + +std::unique_ptr make_dir_net_module(); + +} // namespace ark::detail diff --git a/src/server/dir/interface/dir_net_module.hpp b/src/server/dir/dir_net_module.hpp similarity index 100% rename from src/server/dir/interface/dir_net_module.hpp rename to src/server/dir/dir_net_module.hpp diff --git a/src/server/dir/src/dir_net_module_impl.cpp b/src/server/dir/dir_net_module_impl.cpp similarity index 89% rename from src/server/dir/src/dir_net_module_impl.cpp rename to src/server/dir/dir_net_module_impl.cpp index a48979cf1..eae8af9bc 100644 --- a/src/server/dir/src/dir_net_module_impl.cpp +++ b/src/server/dir/dir_net_module_impl.cpp @@ -18,10 +18,15 @@ * */ -#include "dir/include/dir_net_module_impl.hpp" +#include "dir_module_factory.hpp" +#include "dir_net_module_impl.hpp" namespace ark { +std::unique_ptr detail::make_dir_net_module() { + return std::make_unique(); +} + bool dir_net_module_impl::init() { bus_module_ = find_module(); net_service_manager_module_ = find_module(); diff --git a/src/server/dir/include/dir_net_module_impl.hpp b/src/server/dir/dir_net_module_impl.hpp similarity index 88% rename from src/server/dir/include/dir_net_module_impl.hpp rename to src/server/dir/dir_net_module_impl.hpp index e24d14e87..a5626a194 100644 --- a/src/server/dir/include/dir_net_module_impl.hpp +++ b/src/server/dir/dir_net_module_impl.hpp @@ -22,16 +22,15 @@ #include "ark/bus/bus_module.hpp" #include "ark/bus/msg_module.hpp" +#include "ark/module_base.hpp" #include "ark/net/net_service_manager_module.hpp" -#include "ark/plugin_manager.hpp" #include "ark/proto.hpp" -#include "dir/interface/dir_net_module.hpp" +#include "dir_net_module.hpp" namespace ark { -class dir_net_module_impl final : public dir_net_module +class dir_net_module_impl final : public module_base { - ARK_DECLARE_MODULE_FUNCTIONS public: bool init() override; bool post_init() override; diff --git a/src/server/dir/src/dir_plugin.cpp b/src/server/dir/dir_plugin.cpp similarity index 54% rename from src/server/dir/src/dir_plugin.cpp rename to src/server/dir/dir_plugin.cpp index 31ae52ae5..15b3fecc5 100644 --- a/src/server/dir/src/dir_plugin.cpp +++ b/src/server/dir/dir_plugin.cpp @@ -18,19 +18,34 @@ * */ -#include "dir/include/dir_net_module_impl.hpp" -#include "dir/include/dir_plugin.hpp" +#include + +#include "dir_module_factory.hpp" +#include "dir_plugin.hpp" namespace ark { -ARK_DECLARE_PLUGIN_DLL_FUNCTION(dir_plugin) +ARK_EXPORT_FUNC void DllEntryPlugin(plugin_manager* manager, const std::string& name, logger* log) { + logger::init(log); + manager->register_plugin(name); +} + +ARK_EXPORT_FUNC void DllExitPlugin(plugin_manager* manager) { + manager->unregister_plugin(); +} void dir_plugin::install() { - ARK_REGISTER_MODULE(dir_net_module, dir_net_module_impl); + if (!register_module(detail::make_dir_net_module(), module_update::disabled)) + { + throw std::runtime_error("dir_net_module registration rejected"); + } } void dir_plugin::uninstall() { - ARK_UNREGISTER_MODULE(dir_net_module, dir_net_module_impl); + if (!unregister_module()) + { + throw std::runtime_error("dir_net_module unregistration rejected"); + } } } // namespace ark diff --git a/src/server/dir/include/dir_plugin.hpp b/src/server/dir/dir_plugin.hpp similarity index 82% rename from src/server/dir/include/dir_plugin.hpp rename to src/server/dir/dir_plugin.hpp index 61801a133..c88b7180c 100644 --- a/src/server/dir/include/dir_plugin.hpp +++ b/src/server/dir/dir_plugin.hpp @@ -20,12 +20,15 @@ #pragma once -#include "ark/module.hpp" -#include "ark/plugin.hpp" -#include "ark/plugin_manager.hpp" +#include "ark/plugin_base.hpp" namespace ark { -ARK_DECLARE_PLUGIN(dir_plugin) +class dir_plugin final : public plugin_base +{ +public: + void install() override; + void uninstall() override; +}; } // namespace ark diff --git a/src/server/game/game_module_factory.hpp b/src/server/game/game_module_factory.hpp new file mode 100644 index 000000000..5fc98467b --- /dev/null +++ b/src/server/game/game_module_factory.hpp @@ -0,0 +1,12 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#pragma once + +#include + +#include "game_net_module.hpp" + +namespace ark::detail { + +std::unique_ptr make_game_net_module(); + +} // namespace ark::detail diff --git a/src/server/game/interface/game_net_module.hpp b/src/server/game/game_net_module.hpp similarity index 100% rename from src/server/game/interface/game_net_module.hpp rename to src/server/game/game_net_module.hpp diff --git a/src/server/game/src/game_net_module_impl.cpp b/src/server/game/game_net_module_impl.cpp similarity index 99% rename from src/server/game/src/game_net_module_impl.cpp rename to src/server/game/game_net_module_impl.cpp index 6bf30db13..3f3f66981 100644 --- a/src/server/game/src/game_net_module_impl.cpp +++ b/src/server/game/game_net_module_impl.cpp @@ -20,10 +20,15 @@ #include "ark/event_define.hpp" #include "ark/kernel/data_list_impl.hpp" -#include "game/include/game_net_module_impl.hpp" +#include "game_module_factory.hpp" +#include "game_net_module_impl.hpp" namespace ark { +std::unique_ptr detail::make_game_net_module() { + return std::make_unique(); +} + bool game_net_module_impl::init() { kernel_module_ = find_module(); uuid_module_ = find_module(); diff --git a/src/server/game/include/game_net_module_impl.hpp b/src/server/game/game_net_module_impl.hpp similarity index 96% rename from src/server/game/include/game_net_module_impl.hpp rename to src/server/game/game_net_module_impl.hpp index 2eaf56ed6..1860a29f7 100644 --- a/src/server/game/include/game_net_module_impl.hpp +++ b/src/server/game/game_net_module_impl.hpp @@ -24,17 +24,16 @@ #include "ark/bus/msg_module.hpp" #include "ark/kernel/kernel_module.hpp" #include "ark/kernel/map_module.hpp" +#include "ark/module_base.hpp" #include "ark/net/net_service_manager_module.hpp" -#include "ark/plugin_manager.hpp" #include "ark/proto.hpp" #include "ark/utility/guid_module.hpp" -#include "game/interface/game_net_module.hpp" +#include "game_net_module.hpp" namespace ark { -class game_net_module_impl final : public game_net_module +class game_net_module_impl final : public module_base { - ARK_DECLARE_MODULE_FUNCTIONS public: bool init() override; bool post_init() override; diff --git a/src/server/game/src/game_plugin.cpp b/src/server/game/game_plugin.cpp similarity index 54% rename from src/server/game/src/game_plugin.cpp rename to src/server/game/game_plugin.cpp index a1232579a..ee29a0728 100644 --- a/src/server/game/src/game_plugin.cpp +++ b/src/server/game/game_plugin.cpp @@ -18,19 +18,34 @@ * */ -#include "game/include/game_net_module_impl.hpp" -#include "game/include/game_plugin.hpp" +#include + +#include "game_module_factory.hpp" +#include "game_plugin.hpp" namespace ark { -ARK_DECLARE_PLUGIN_DLL_FUNCTION(game_plugin) +ARK_EXPORT_FUNC void DllEntryPlugin(plugin_manager* manager, const std::string& name, logger* log) { + logger::init(log); + manager->register_plugin(name); +} + +ARK_EXPORT_FUNC void DllExitPlugin(plugin_manager* manager) { + manager->unregister_plugin(); +} void game_plugin::install() { - ARK_REGISTER_MODULE(game_net_module, game_net_module_impl); + if (!register_module(detail::make_game_net_module(), module_update::disabled)) + { + throw std::runtime_error("game_net_module registration rejected"); + } } void game_plugin::uninstall() { - ARK_UNREGISTER_MODULE(game_net_module, game_net_module_impl); + if (!unregister_module()) + { + throw std::runtime_error("game_net_module unregistration rejected"); + } } } // namespace ark diff --git a/src/server/game/include/game_plugin.hpp b/src/server/game/game_plugin.hpp similarity index 82% rename from src/server/game/include/game_plugin.hpp rename to src/server/game/game_plugin.hpp index cd742f666..43e74559a 100644 --- a/src/server/game/include/game_plugin.hpp +++ b/src/server/game/game_plugin.hpp @@ -20,12 +20,15 @@ #pragma once -#include "ark/module.hpp" -#include "ark/plugin.hpp" -#include "ark/plugin_manager.hpp" +#include "ark/plugin_base.hpp" namespace ark { -ARK_DECLARE_PLUGIN(game_plugin) +class game_plugin final : public plugin_base +{ +public: + void install() override; + void uninstall() override; +}; } // namespace ark diff --git a/src/server/login/login_module_factory.hpp b/src/server/login/login_module_factory.hpp new file mode 100644 index 000000000..6f3db19ed --- /dev/null +++ b/src/server/login/login_module_factory.hpp @@ -0,0 +1,12 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#pragma once + +#include + +#include "login_net_module.hpp" + +namespace ark::detail { + +std::unique_ptr make_login_net_module(); + +} // namespace ark::detail diff --git a/src/server/login/interface/login_net_module.hpp b/src/server/login/login_net_module.hpp similarity index 100% rename from src/server/login/interface/login_net_module.hpp rename to src/server/login/login_net_module.hpp diff --git a/src/server/login/src/login_net_module_impl.cpp b/src/server/login/login_net_module_impl.cpp similarity index 92% rename from src/server/login/src/login_net_module_impl.cpp rename to src/server/login/login_net_module_impl.cpp index e1a8ac38c..c8d08edb5 100644 --- a/src/server/login/src/login_net_module_impl.cpp +++ b/src/server/login/login_net_module_impl.cpp @@ -18,10 +18,15 @@ * */ -#include "login/include/login_net_module_impl.hpp" +#include "login_module_factory.hpp" +#include "login_net_module_impl.hpp" namespace ark { +std::unique_ptr detail::make_login_net_module() { + return std::make_unique(); +} + bool login_net_module_impl::init() { bus_module_ = find_module(); net_service_manager_module_ = find_module(); diff --git a/src/server/login/include/login_net_module_impl.hpp b/src/server/login/login_net_module_impl.hpp similarity index 89% rename from src/server/login/include/login_net_module_impl.hpp rename to src/server/login/login_net_module_impl.hpp index 15c0f3a2c..4a031061c 100644 --- a/src/server/login/include/login_net_module_impl.hpp +++ b/src/server/login/login_net_module_impl.hpp @@ -22,16 +22,15 @@ #include "ark/bus/bus_module.hpp" #include "ark/bus/msg_module.hpp" +#include "ark/module_base.hpp" #include "ark/net/net_service_manager_module.hpp" -#include "ark/plugin_manager.hpp" #include "ark/proto.hpp" -#include "login/interface/login_net_module.hpp" +#include "login_net_module.hpp" namespace ark { -class login_net_module_impl final : public login_net_module +class login_net_module_impl final : public module_base { - ARK_DECLARE_MODULE_FUNCTIONS public: bool init() override; bool post_init() override; diff --git a/src/server/login/src/login_plugin.cpp b/src/server/login/login_plugin.cpp similarity index 54% rename from src/server/login/src/login_plugin.cpp rename to src/server/login/login_plugin.cpp index 970ca17a5..0aa99e8a1 100644 --- a/src/server/login/src/login_plugin.cpp +++ b/src/server/login/login_plugin.cpp @@ -18,19 +18,34 @@ * */ -#include "login/include/login_net_module_impl.hpp" -#include "login/include/login_plugin.hpp" +#include + +#include "login_module_factory.hpp" +#include "login_plugin.hpp" namespace ark { -ARK_DECLARE_PLUGIN_DLL_FUNCTION(login_plugin) +ARK_EXPORT_FUNC void DllEntryPlugin(plugin_manager* manager, const std::string& name, logger* log) { + logger::init(log); + manager->register_plugin(name); +} + +ARK_EXPORT_FUNC void DllExitPlugin(plugin_manager* manager) { + manager->unregister_plugin(); +} void login_plugin::install() { - ARK_REGISTER_MODULE(login_net_module, login_net_module_impl); + if (!register_module(detail::make_login_net_module(), module_update::disabled)) + { + throw std::runtime_error("login_net_module registration rejected"); + } } void login_plugin::uninstall() { - ARK_UNREGISTER_MODULE(login_net_module, login_net_module_impl); + if (!unregister_module()) + { + throw std::runtime_error("login_net_module unregistration rejected"); + } } } // namespace ark diff --git a/src/server/login/include/login_plugin.hpp b/src/server/login/login_plugin.hpp similarity index 82% rename from src/server/login/include/login_plugin.hpp rename to src/server/login/login_plugin.hpp index e874a1040..aa6661737 100644 --- a/src/server/login/include/login_plugin.hpp +++ b/src/server/login/login_plugin.hpp @@ -20,12 +20,15 @@ #pragma once -#include "ark/module.hpp" -#include "ark/plugin.hpp" -#include "ark/plugin_manager.hpp" +#include "ark/plugin_base.hpp" namespace ark { -ARK_DECLARE_PLUGIN(login_plugin) +class login_plugin final : public plugin_base +{ +public: + void install() override; + void uninstall() override; +}; } // namespace ark diff --git a/src/server/master/master_module_factory.hpp b/src/server/master/master_module_factory.hpp new file mode 100644 index 000000000..8cf45d6fb --- /dev/null +++ b/src/server/master/master_module_factory.hpp @@ -0,0 +1,12 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#pragma once + +#include + +#include "master_net_module.hpp" + +namespace ark::detail { + +std::unique_ptr make_master_net_module(); + +} // namespace ark::detail diff --git a/src/server/master/interface/master_net_module.hpp b/src/server/master/master_net_module.hpp similarity index 100% rename from src/server/master/interface/master_net_module.hpp rename to src/server/master/master_net_module.hpp diff --git a/src/server/master/src/master_net_module_impl.cpp b/src/server/master/master_net_module_impl.cpp similarity index 94% rename from src/server/master/src/master_net_module_impl.cpp rename to src/server/master/master_net_module_impl.cpp index 7bc28424e..aef1823ce 100644 --- a/src/server/master/src/master_net_module_impl.cpp +++ b/src/server/master/master_net_module_impl.cpp @@ -18,10 +18,15 @@ * */ -#include "master/include/master_net_module_impl.hpp" +#include "master_module_factory.hpp" +#include "master_net_module_impl.hpp" namespace ark { +std::unique_ptr detail::make_master_net_module() { + return std::make_unique(); +} + bool master_net_module_impl::init() { bus_module_ = find_module(); net_service_manager_module_ = find_module(); diff --git a/src/server/master/include/master_net_module_impl.hpp b/src/server/master/master_net_module_impl.hpp similarity index 91% rename from src/server/master/include/master_net_module_impl.hpp rename to src/server/master/master_net_module_impl.hpp index ae058071b..47dfebb78 100644 --- a/src/server/master/include/master_net_module_impl.hpp +++ b/src/server/master/master_net_module_impl.hpp @@ -23,17 +23,16 @@ #include "ark/bus/bus_module.hpp" #include "ark/bus/msg_module.hpp" #include "ark/container/map.hpp" +#include "ark/module_base.hpp" #include "ark/net/net_service_manager_module.hpp" -#include "ark/plugin_manager.hpp" #include "ark/proto.hpp" #include "ark/utility/timer_module.hpp" -#include "master/interface/master_net_module.hpp" +#include "master_net_module.hpp" namespace ark { -class master_net_module_impl final : public master_net_module +class master_net_module_impl final : public module_base { - ARK_DECLARE_MODULE_FUNCTIONS public: bool init() override; bool post_init() override; diff --git a/src/server/master/src/master_plugin.cpp b/src/server/master/master_plugin.cpp similarity index 53% rename from src/server/master/src/master_plugin.cpp rename to src/server/master/master_plugin.cpp index b52b69777..8e700c69e 100644 --- a/src/server/master/src/master_plugin.cpp +++ b/src/server/master/master_plugin.cpp @@ -18,19 +18,34 @@ * */ -#include "master/include/master_net_module_impl.hpp" -#include "master/include/master_plugin.hpp" +#include + +#include "master_module_factory.hpp" +#include "master_plugin.hpp" namespace ark { -ARK_DECLARE_PLUGIN_DLL_FUNCTION(master_plugin) +ARK_EXPORT_FUNC void DllEntryPlugin(plugin_manager* manager, const std::string& name, logger* log) { + logger::init(log); + manager->register_plugin(name); +} + +ARK_EXPORT_FUNC void DllExitPlugin(plugin_manager* manager) { + manager->unregister_plugin(); +} void master_plugin::install() { - ARK_REGISTER_MODULE(master_net_module, master_net_module_impl); + if (!register_module(detail::make_master_net_module(), module_update::disabled)) + { + throw std::runtime_error("master_net_module registration rejected"); + } } void master_plugin::uninstall() { - ARK_UNREGISTER_MODULE(master_net_module, master_net_module_impl); + if (!unregister_module()) + { + throw std::runtime_error("master_net_module unregistration rejected"); + } } } // namespace ark diff --git a/src/server/master/include/master_plugin.hpp b/src/server/master/master_plugin.hpp similarity index 81% rename from src/server/master/include/master_plugin.hpp rename to src/server/master/master_plugin.hpp index f9155db3b..0781481e4 100755 --- a/src/server/master/include/master_plugin.hpp +++ b/src/server/master/master_plugin.hpp @@ -20,11 +20,15 @@ #pragma once -#include "ark/plugin.hpp" -#include "ark/plugin_manager.hpp" +#include "ark/plugin_base.hpp" namespace ark { -ARK_DECLARE_PLUGIN(master_plugin) +class master_plugin final : public plugin_base +{ +public: + void install() override; + void uninstall() override; +}; } // namespace ark diff --git a/src/server/proxy/proxy_module_factory.hpp b/src/server/proxy/proxy_module_factory.hpp new file mode 100644 index 000000000..0675313e9 --- /dev/null +++ b/src/server/proxy/proxy_module_factory.hpp @@ -0,0 +1,12 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#pragma once + +#include + +#include "proxy_net_module.hpp" + +namespace ark::detail { + +std::unique_ptr make_proxy_net_module(); + +} // namespace ark::detail diff --git a/src/server/proxy/interface/proxy_net_module.hpp b/src/server/proxy/proxy_net_module.hpp similarity index 100% rename from src/server/proxy/interface/proxy_net_module.hpp rename to src/server/proxy/proxy_net_module.hpp diff --git a/src/server/proxy/src/proxy_net_module_impl.cpp b/src/server/proxy/proxy_net_module_impl.cpp similarity index 95% rename from src/server/proxy/src/proxy_net_module_impl.cpp rename to src/server/proxy/proxy_net_module_impl.cpp index da08ed24f..e7d1fb6df 100644 --- a/src/server/proxy/src/proxy_net_module_impl.cpp +++ b/src/server/proxy/proxy_net_module_impl.cpp @@ -18,10 +18,15 @@ * */ -#include "proxy/include/proxy_net_module_impl.hpp" +#include "proxy_module_factory.hpp" +#include "proxy_net_module_impl.hpp" namespace ark { +std::unique_ptr detail::make_proxy_net_module() { + return std::make_unique(); +} + bool proxy_net_module_impl::init() { bus_module_ = find_module(); msg_module_ = find_module(); diff --git a/src/server/proxy/include/proxy_net_module_impl.hpp b/src/server/proxy/proxy_net_module_impl.hpp similarity index 93% rename from src/server/proxy/include/proxy_net_module_impl.hpp rename to src/server/proxy/proxy_net_module_impl.hpp index bacfb4d69..794cc8044 100644 --- a/src/server/proxy/include/proxy_net_module_impl.hpp +++ b/src/server/proxy/proxy_net_module_impl.hpp @@ -23,16 +23,15 @@ #include "ark/bus/bus_module.hpp" #include "ark/bus/msg_module.hpp" #include "ark/container/consistent_hash.hpp" +#include "ark/module_base.hpp" #include "ark/net/net_service_manager_module.hpp" -#include "ark/plugin_manager.hpp" #include "ark/proto.hpp" -#include "proxy/interface/proxy_net_module.hpp" +#include "proxy_net_module.hpp" namespace ark { -class proxy_net_module_impl final : public proxy_net_module +class proxy_net_module_impl final : public module_base { - ARK_DECLARE_MODULE_FUNCTIONS public: bool init() override; bool post_init() override; diff --git a/src/server/proxy/src/proxy_plugin.cpp b/src/server/proxy/proxy_plugin.cpp similarity index 54% rename from src/server/proxy/src/proxy_plugin.cpp rename to src/server/proxy/proxy_plugin.cpp index b36531007..2f2fcc773 100644 --- a/src/server/proxy/src/proxy_plugin.cpp +++ b/src/server/proxy/proxy_plugin.cpp @@ -18,19 +18,34 @@ * */ -#include "proxy/include/proxy_net_module_impl.hpp" -#include "proxy/include/proxy_plugin.hpp" +#include + +#include "proxy_module_factory.hpp" +#include "proxy_plugin.hpp" namespace ark { -ARK_DECLARE_PLUGIN_DLL_FUNCTION(proxy_plugin) +ARK_EXPORT_FUNC void DllEntryPlugin(plugin_manager* manager, const std::string& name, logger* log) { + logger::init(log); + manager->register_plugin(name); +} + +ARK_EXPORT_FUNC void DllExitPlugin(plugin_manager* manager) { + manager->unregister_plugin(); +} void proxy_plugin::install() { - ARK_REGISTER_MODULE(proxy_net_module, proxy_net_module_impl); + if (!register_module(detail::make_proxy_net_module(), module_update::disabled)) + { + throw std::runtime_error("proxy_net_module registration rejected"); + } } void proxy_plugin::uninstall() { - ARK_UNREGISTER_MODULE(proxy_net_module, proxy_net_module_impl); + if (!unregister_module()) + { + throw std::runtime_error("proxy_net_module unregistration rejected"); + } } } // namespace ark diff --git a/src/server/proxy/include/proxy_plugin.hpp b/src/server/proxy/proxy_plugin.hpp similarity index 82% rename from src/server/proxy/include/proxy_plugin.hpp rename to src/server/proxy/proxy_plugin.hpp index cc115f626..9c7d048fd 100755 --- a/src/server/proxy/include/proxy_plugin.hpp +++ b/src/server/proxy/proxy_plugin.hpp @@ -20,11 +20,15 @@ #pragma once -#include "ark/plugin.hpp" -#include "ark/plugin_manager.hpp" +#include "ark/plugin_base.hpp" namespace ark { -ARK_DECLARE_PLUGIN(proxy_plugin) +class proxy_plugin final : public plugin_base +{ +public: + void install() override; + void uninstall() override; +}; } // namespace ark diff --git a/src/server/router/router_module_factory.hpp b/src/server/router/router_module_factory.hpp new file mode 100644 index 000000000..6232f5a7b --- /dev/null +++ b/src/server/router/router_module_factory.hpp @@ -0,0 +1,12 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#pragma once + +#include + +#include "router_net_module.hpp" + +namespace ark::detail { + +std::unique_ptr make_router_net_module(); + +} // namespace ark::detail diff --git a/src/server/router/interface/router_net_module.hpp b/src/server/router/router_net_module.hpp similarity index 100% rename from src/server/router/interface/router_net_module.hpp rename to src/server/router/router_net_module.hpp diff --git a/src/server/router/src/router_net_module_impl.cpp b/src/server/router/router_net_module_impl.cpp similarity index 92% rename from src/server/router/src/router_net_module_impl.cpp rename to src/server/router/router_net_module_impl.cpp index 9e67c2d2b..61cf23999 100644 --- a/src/server/router/src/router_net_module_impl.cpp +++ b/src/server/router/router_net_module_impl.cpp @@ -18,10 +18,15 @@ * */ -#include "router/include/router_net_module_impl.hpp" +#include "router_module_factory.hpp" +#include "router_net_module_impl.hpp" namespace ark { +std::unique_ptr detail::make_router_net_module() { + return std::make_unique(); +} + bool router_net_module_impl::init() { bus_module_ = find_module(); net_service_manager_module_ = find_module(); diff --git a/src/server/router/include/router_net_module_impl.hpp b/src/server/router/router_net_module_impl.hpp similarity index 87% rename from src/server/router/include/router_net_module_impl.hpp rename to src/server/router/router_net_module_impl.hpp index dafc3848e..7fe7f8853 100644 --- a/src/server/router/include/router_net_module_impl.hpp +++ b/src/server/router/router_net_module_impl.hpp @@ -22,16 +22,15 @@ #include "ark/bus/bus_module.hpp" #include "ark/bus/msg_module.hpp" +#include "ark/module_base.hpp" #include "ark/net/net_service_manager_module.hpp" -#include "ark/plugin_manager.hpp" #include "ark/proto.hpp" -#include "router/interface/router_net_module.hpp" +#include "router_net_module.hpp" namespace ark { -class router_net_module_impl final : public router_net_module +class router_net_module_impl final : public module_base { - ARK_DECLARE_MODULE_FUNCTIONS public: bool init() override; bool post_init() override; diff --git a/src/server/router/src/router_plugin.cpp b/src/server/router/router_plugin.cpp similarity index 53% rename from src/server/router/src/router_plugin.cpp rename to src/server/router/router_plugin.cpp index e4915f422..f182085f7 100644 --- a/src/server/router/src/router_plugin.cpp +++ b/src/server/router/router_plugin.cpp @@ -18,19 +18,34 @@ * */ -#include "router/include/router_net_module_impl.hpp" -#include "router/include/router_plugin.hpp" +#include + +#include "router_module_factory.hpp" +#include "router_plugin.hpp" namespace ark { -ARK_DECLARE_PLUGIN_DLL_FUNCTION(router_plugin) +ARK_EXPORT_FUNC void DllEntryPlugin(plugin_manager* manager, const std::string& name, logger* log) { + logger::init(log); + manager->register_plugin(name); +} + +ARK_EXPORT_FUNC void DllExitPlugin(plugin_manager* manager) { + manager->unregister_plugin(); +} void router_plugin::install() { - ARK_REGISTER_MODULE(router_net_module, router_net_module_impl); + if (!register_module(detail::make_router_net_module(), module_update::disabled)) + { + throw std::runtime_error("router_net_module registration rejected"); + } } void router_plugin::uninstall() { - ARK_UNREGISTER_MODULE(router_net_module, router_net_module_impl); + if (!unregister_module()) + { + throw std::runtime_error("router_net_module unregistration rejected"); + } } } // namespace ark diff --git a/src/server/router/include/router_plugin.hpp b/src/server/router/router_plugin.hpp similarity index 81% rename from src/server/router/include/router_plugin.hpp rename to src/server/router/router_plugin.hpp index 54d52ac9d..07fa1bcff 100755 --- a/src/server/router/include/router_plugin.hpp +++ b/src/server/router/router_plugin.hpp @@ -20,11 +20,15 @@ #pragma once -#include "ark/plugin.hpp" -#include "ark/plugin_manager.hpp" +#include "ark/plugin_base.hpp" namespace ark { -ARK_DECLARE_PLUGIN(router_plugin) +class router_plugin final : public plugin_base +{ +public: + void install() override; + void uninstall() override; +}; } // namespace ark diff --git a/src/server/simulate/include/simulate_define.hpp b/src/server/simulate/simulate_define.hpp similarity index 100% rename from src/server/simulate/include/simulate_define.hpp rename to src/server/simulate/simulate_define.hpp diff --git a/src/server/simulate/interface/simulate_module.hpp b/src/server/simulate/simulate_module.hpp similarity index 96% rename from src/server/simulate/interface/simulate_module.hpp rename to src/server/simulate/simulate_module.hpp index 9cc05aac6..37a4c75ad 100644 --- a/src/server/simulate/interface/simulate_module.hpp +++ b/src/server/simulate/simulate_module.hpp @@ -22,8 +22,8 @@ #include "ark/module.hpp" #include "ark/physic/physic_module.hpp" -#include "simulate/include/simulate_define.hpp" -#include "simulate/include/simulate_msg_define.hpp" +#include "simulate_define.hpp" +#include "simulate_msg_define.hpp" namespace ark { diff --git a/src/server/simulate/simulate_module_factory.hpp b/src/server/simulate/simulate_module_factory.hpp new file mode 100644 index 000000000..636302238 --- /dev/null +++ b/src/server/simulate/simulate_module_factory.hpp @@ -0,0 +1,14 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#pragma once + +#include + +#include "simulate_module.hpp" +#include "simulate_net_module.hpp" + +namespace ark::detail { + +std::unique_ptr make_simulate_net_module(); +std::unique_ptr make_simulate_module(); + +} // namespace ark::detail diff --git a/src/server/simulate/src/simulate_module_impl.cpp b/src/server/simulate/simulate_module_impl.cpp similarity index 98% rename from src/server/simulate/src/simulate_module_impl.cpp rename to src/server/simulate/simulate_module_impl.cpp index 4a652d377..5e1cce691 100644 --- a/src/server/simulate/src/simulate_module_impl.cpp +++ b/src/server/simulate/simulate_module_impl.cpp @@ -19,10 +19,15 @@ */ #include "ark/plugin_manager.hpp" -#include "simulate/include/simulate_module_impl.hpp" +#include "simulate_module_factory.hpp" +#include "simulate_module_impl.hpp" namespace ark { +std::unique_ptr detail::make_simulate_module() { + return std::make_unique(); +} + //method realization #define GET_AND_CHECK_OBJ_EXIST(scene_id, index) \ diff --git a/src/server/simulate/include/simulate_module_impl.hpp b/src/server/simulate/simulate_module_impl.hpp similarity index 94% rename from src/server/simulate/include/simulate_module_impl.hpp rename to src/server/simulate/simulate_module_impl.hpp index 09dd83dd4..bba03e51b 100644 --- a/src/server/simulate/include/simulate_module_impl.hpp +++ b/src/server/simulate/simulate_module_impl.hpp @@ -20,15 +20,14 @@ #pragma once +#include "ark/module_base.hpp" #include "ark/physic/physic_module.hpp" -#include "ark/plugin_manager.hpp" -#include "simulate/interface/simulate_module.hpp" +#include "simulate_module.hpp" namespace ark { -class simulate_module_impl final : public simulate_module +class simulate_module_impl final : public module_base { - ARK_DECLARE_MODULE_FUNCTIONS public: bool init() override; diff --git a/src/server/simulate/include/simulate_msg_define.hpp b/src/server/simulate/simulate_msg_define.hpp similarity index 100% rename from src/server/simulate/include/simulate_msg_define.hpp rename to src/server/simulate/simulate_msg_define.hpp diff --git a/src/server/simulate/interface/simulate_net_module.hpp b/src/server/simulate/simulate_net_module.hpp similarity index 100% rename from src/server/simulate/interface/simulate_net_module.hpp rename to src/server/simulate/simulate_net_module.hpp diff --git a/src/server/simulate/src/simulate_net_module_impl.cpp b/src/server/simulate/simulate_net_module_impl.cpp similarity index 98% rename from src/server/simulate/src/simulate_net_module_impl.cpp rename to src/server/simulate/simulate_net_module_impl.cpp index dc7b663bf..0e587dd7d 100644 --- a/src/server/simulate/src/simulate_net_module_impl.cpp +++ b/src/server/simulate/simulate_net_module_impl.cpp @@ -18,10 +18,15 @@ * */ -#include "simulate/include/simulate_net_module_impl.hpp" +#include "simulate_module_factory.hpp" +#include "simulate_net_module_impl.hpp" namespace ark { +std::unique_ptr detail::make_simulate_net_module() { + return std::make_unique(); +} + //method realization bool simulate_net_module_impl::init() { diff --git a/src/server/simulate/include/simulate_net_module_impl.hpp b/src/server/simulate/simulate_net_module_impl.hpp similarity index 90% rename from src/server/simulate/include/simulate_net_module_impl.hpp rename to src/server/simulate/simulate_net_module_impl.hpp index ddb643b77..37cde3a98 100644 --- a/src/server/simulate/include/simulate_net_module_impl.hpp +++ b/src/server/simulate/simulate_net_module_impl.hpp @@ -21,17 +21,16 @@ #pragma once #include "ark/bus/bus_module.hpp" +#include "ark/module_base.hpp" #include "ark/net/net_service_manager_module.hpp" -#include "ark/plugin_manager.hpp" -#include "simulate/interface/simulate_module.hpp" -#include "simulate/interface/simulate_net_module.hpp" +#include "simulate_module.hpp" #include "simulate_msg_define.hpp" +#include "simulate_net_module.hpp" namespace ark { -class simulate_net_module_impl final : public simulate_net_module +class simulate_net_module_impl final : public module_base { - ARK_DECLARE_MODULE_FUNCTIONS public: bool init() override; bool post_init() override; diff --git a/src/server/simulate/simulate_plugin.cpp b/src/server/simulate/simulate_plugin.cpp new file mode 100644 index 000000000..a0cba4f9b --- /dev/null +++ b/src/server/simulate/simulate_plugin.cpp @@ -0,0 +1,59 @@ +/* + * This source file is part of ARK + * For the latest info, see https://github.com/ArkNX + * + * Copyright (c) 2013-2020 ArkNX authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +#include "simulate_module_factory.hpp" +#include "simulate_plugin.hpp" + +namespace ark { + +ARK_EXPORT_FUNC void DllEntryPlugin(plugin_manager* manager, const std::string& name, logger* log) { + logger::init(log); + manager->register_plugin(name); +} + +ARK_EXPORT_FUNC void DllExitPlugin(plugin_manager* manager) { + manager->unregister_plugin(); +} + +void simulate_plugin::install() { + if (!register_module(detail::make_simulate_net_module(), module_update::disabled)) + { + throw std::runtime_error("simulate_net_module registration rejected"); + } + if (!register_module(detail::make_simulate_module(), module_update::disabled)) + { + throw std::runtime_error("simulate_module registration rejected"); + } +} + +void simulate_plugin::uninstall() { + if (!unregister_module()) + { + throw std::runtime_error("simulate_module unregistration rejected"); + } + if (!unregister_module()) + { + throw std::runtime_error("simulate_net_module unregistration rejected"); + } +} + +} // namespace ark diff --git a/src/server/simulate/include/simulate_plugin.hpp b/src/server/simulate/simulate_plugin.hpp similarity index 81% rename from src/server/simulate/include/simulate_plugin.hpp rename to src/server/simulate/simulate_plugin.hpp index a9905765e..bc5e445c2 100644 --- a/src/server/simulate/include/simulate_plugin.hpp +++ b/src/server/simulate/simulate_plugin.hpp @@ -20,11 +20,15 @@ #pragma once -#include "ark/plugin.hpp" -#include "ark/plugin_manager.hpp" +#include "ark/plugin_base.hpp" namespace ark { -ARK_DECLARE_PLUGIN(simulate_plugin) +class simulate_plugin final : public plugin_base +{ +public: + void install() override; + void uninstall() override; +}; } // namespace ark diff --git a/src/server/simulate/src/simulate_plugin.cpp b/src/server/simulate/src/simulate_plugin.cpp deleted file mode 100644 index 225e6dc72..000000000 --- a/src/server/simulate/src/simulate_plugin.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/* - * This source file is part of ARK - * For the latest info, see https://github.com/ArkNX - * - * Copyright (c) 2013-2020 ArkNX authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"), - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -#include "simulate/include/simulate_module_impl.hpp" -#include "simulate/include/simulate_net_module_impl.hpp" -#include "simulate/include/simulate_plugin.hpp" - -namespace ark { - -ARK_DECLARE_PLUGIN_DLL_FUNCTION(simulate_plugin) - -void simulate_plugin::install() { - // install module - ARK_REGISTER_MODULE(simulate_net_module, simulate_net_module_impl); - ARK_REGISTER_MODULE(simulate_module, simulate_module_impl); -} - -void simulate_plugin::uninstall() { - // uninstall module - ARK_UNREGISTER_MODULE(simulate_module, simulate_module_impl); - ARK_UNREGISTER_MODULE(simulate_net_module, simulate_net_module_impl); -} - -} // namespace ark diff --git a/src/server/world/world_module_factory.hpp b/src/server/world/world_module_factory.hpp new file mode 100644 index 000000000..5294a8cd3 --- /dev/null +++ b/src/server/world/world_module_factory.hpp @@ -0,0 +1,12 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#pragma once + +#include + +#include "world_net_module.hpp" + +namespace ark::detail { + +std::unique_ptr make_world_net_module(); + +} // namespace ark::detail diff --git a/src/server/world/interface/world_net_module.hpp b/src/server/world/world_net_module.hpp similarity index 100% rename from src/server/world/interface/world_net_module.hpp rename to src/server/world/world_net_module.hpp diff --git a/src/server/world/src/world_net_module_impl.cpp b/src/server/world/world_net_module_impl.cpp similarity index 97% rename from src/server/world/src/world_net_module_impl.cpp rename to src/server/world/world_net_module_impl.cpp index 3b8980e8f..9a126305f 100644 --- a/src/server/world/src/world_net_module_impl.cpp +++ b/src/server/world/world_net_module_impl.cpp @@ -18,10 +18,15 @@ * */ -#include "world/include/world_net_module_impl.hpp" +#include "world_module_factory.hpp" +#include "world_net_module_impl.hpp" namespace ark { +std::unique_ptr detail::make_world_net_module() { + return std::make_unique(); +} + bool world_net_module_impl::init() { last_check_time_ = get_plugin_manager()->get_now_time(); diff --git a/src/server/world/include/world_net_module_impl.hpp b/src/server/world/world_net_module_impl.hpp similarity index 93% rename from src/server/world/include/world_net_module_impl.hpp rename to src/server/world/world_net_module_impl.hpp index c627258b3..cb4c952e4 100644 --- a/src/server/world/include/world_net_module_impl.hpp +++ b/src/server/world/world_net_module_impl.hpp @@ -23,17 +23,16 @@ #include "ark/bus/bus_module.hpp" #include "ark/bus/msg_module.hpp" #include "ark/kernel/kernel_module.hpp" +#include "ark/module_base.hpp" #include "ark/net/net_service_manager_module.hpp" -#include "ark/plugin_manager.hpp" #include "ark/proto.hpp" #include "ark/utility/timer_module.hpp" -#include "world/interface/world_net_module.hpp" +#include "world_net_module.hpp" namespace ark { -class world_net_module_impl final : public world_net_module +class world_net_module_impl final : public module_base { - ARK_DECLARE_MODULE_FUNCTIONS public: bool init() override; bool post_init() override; diff --git a/src/server/world/src/world_plugin.cpp b/src/server/world/world_plugin.cpp similarity index 54% rename from src/server/world/src/world_plugin.cpp rename to src/server/world/world_plugin.cpp index 7fc382b88..9a7d3c884 100644 --- a/src/server/world/src/world_plugin.cpp +++ b/src/server/world/world_plugin.cpp @@ -18,19 +18,34 @@ * */ -#include "world/include/world_net_module_impl.hpp" -#include "world/include/world_plugin.hpp" +#include + +#include "world_module_factory.hpp" +#include "world_plugin.hpp" namespace ark { -ARK_DECLARE_PLUGIN_DLL_FUNCTION(world_plugin) +ARK_EXPORT_FUNC void DllEntryPlugin(plugin_manager* manager, const std::string& name, logger* log) { + logger::init(log); + manager->register_plugin(name); +} + +ARK_EXPORT_FUNC void DllExitPlugin(plugin_manager* manager) { + manager->unregister_plugin(); +} void world_plugin::install() { - ARK_REGISTER_MODULE(world_net_module, world_net_module_impl); + if (!register_module(detail::make_world_net_module(), module_update::disabled)) + { + throw std::runtime_error("world_net_module registration rejected"); + } } void world_plugin::uninstall() { - ARK_UNREGISTER_MODULE(world_net_module, world_net_module_impl); + if (!unregister_module()) + { + throw std::runtime_error("world_net_module unregistration rejected"); + } } } // namespace ark diff --git a/src/server/world/include/world_plugin.hpp b/src/server/world/world_plugin.hpp similarity index 82% rename from src/server/world/include/world_plugin.hpp rename to src/server/world/world_plugin.hpp index b45e7d066..1652b5c96 100755 --- a/src/server/world/include/world_plugin.hpp +++ b/src/server/world/world_plugin.hpp @@ -20,11 +20,15 @@ #pragma once -#include "ark/plugin.hpp" -#include "ark/plugin_manager.hpp" +#include "ark/plugin_base.hpp" namespace ark { -ARK_DECLARE_PLUGIN(world_plugin) +class world_plugin final : public plugin_base +{ +public: + void install() override; + void uninstall() override; +}; } // namespace ark diff --git a/tests/ci/server-construction.test.mjs b/tests/ci/server-construction.test.mjs new file mode 100644 index 000000000..f33aef0a8 --- /dev/null +++ b/tests/ci/server-construction.test.mjs @@ -0,0 +1,105 @@ +import assert from 'node:assert/strict'; +import { execFileSync } from 'node:child_process'; +import { existsSync, mkdtempSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import test from 'node:test'; + +const root = resolve(dirname(fileURLToPath(import.meta.url)), '../..'); +const families = ['game', 'login', 'proxy', 'world', 'master', 'dir', 'router', 'simulate']; +const source = path => readFileSync(join(root, path), 'utf8'); + +// Configure-only evidence complements the native construction/DSO tests; no +// compiler, protocol services, PhysX or MessagePack dependency is involved. +test('server compatibility forwarding preserves every old header without publishing factory headers', () => { + const fixture = mkdtempSync(join(tmpdir(), 'ark-server-mapping-')); + try { + writeFileSync(join(fixture, 'CMakeLists.txt'), `cmake_minimum_required(VERSION 3.21) +project(server_mapping LANGUAGES NONE) +include("${root}/src/server/compat_headers.cmake") +get_target_property(roots ark_server_compat_headers INTERFACE_INCLUDE_DIRECTORIES) +file(WRITE "\${CMAKE_BINARY_DIR}/roots.txt" "\${roots}") +`); + const build = join(fixture, 'build'); + const mapping = join(build, 'compat-include'); + mkdirSync(mapping, { recursive: true }); + writeFileSync(join(mapping, 'obsolete.hpp'), 'stale mapping'); + execFileSync('cmake', ['-S', fixture, '-B', build], { stdio: 'pipe' }); + assert.equal(readFileSync(join(build, 'roots.txt'), 'utf8'), mapping); + assert.equal(existsSync(join(mapping, 'obsolete.hpp')), false); + let headers = 0; + for (const family of families) { + for (const header of readdirSync(join(root, 'src/server', family)).filter(name => name.endsWith('.hpp') && !name.endsWith('_factory.hpp'))) { + const category = header.endsWith('_impl.hpp') || header.endsWith('_plugin.hpp') || header.includes('_define.hpp') ? 'include' : 'interface'; + const mapped = readFileSync(join(mapping, family, category, header), 'utf8'); + assert.match(mapped, new RegExp(`#include "${root}/src/server/${family}/${header}"`)); + headers++; + } + assert.equal(existsSync(join(mapping, family, 'include', `${family}_module_factory.hpp`)), false); + assert.equal(existsSync(join(root, 'src/server', family, 'include')), false); + assert.equal(existsSync(join(root, 'src/server', family, 'interface')), false); + assert.equal(existsSync(join(root, 'src/server', family, 'src')), false); + } + assert.equal(headers, 28); + execFileSync('cmake', ['-S', fixture, '-B', build], { stdio: 'pipe' }); + assert.equal(existsSync(join(mapping, 'login/include/login_plugin.hpp')), true); + } finally { + rmSync(fixture, { recursive: true, force: true }); + } +}); + +test('all eight server assemblies use typed factories, disabled updates and explicit dynamic entry points', () => { + for (const family of families) { + const assembly = source(`src/server/${family}/${family}_plugin.cpp`); + const declaration = source(`src/server/${family}/${family}_plugin.hpp`); + assert.match(declaration, new RegExp(`public plugin_base<${family}_plugin>`)); + assert.match(assembly, /ARK_EXPORT_FUNC void DllEntryPlugin\(plugin_manager\* manager, const std::string& name, logger\* log\)/); + assert.match(assembly, /logger::init\(log\)/); + assert.match(assembly, new RegExp(`manager->register_plugin<${family}_plugin>\\(name\\)`)); + assert.match(assembly, /ARK_EXPORT_FUNC void DllExitPlugin\(plugin_manager\* manager\)/); + assert.match(assembly, new RegExp(`manager->unregister_plugin<${family}_plugin>\\(\\)`)); + assert.doesNotMatch(assembly + declaration, /ARK_DECLARE_PLUGIN|ARK_REGISTER_MODULE|ARK_UNREGISTER_MODULE/); + const modules = [`${family}_net_module`, ...(family === 'simulate' ? ['simulate_module'] : [])]; + for (const module of modules) { + assert.match(assembly, new RegExp(`register_module\\(detail::make_${module}\\(\\), module_update::disabled\\)`)); + assert.match(assembly, new RegExp(`unregister_module<${module}>\\(\\)`)); + const implementation = source(`src/server/${family}/${module}_impl.hpp`); + assert.match(implementation, new RegExp(`public module_base<${module}>`)); + assert.doesNotMatch(implementation, /ARK_DECLARE_MODULE_FUNCTIONS|bool update\(\)/); + assert.match(source(`src/server/${family}/${module}_impl.cpp`), new RegExp(`return std::make_unique<${module}_impl>\\(\\)`)); + } + } +}); + +test('native acceptance covers every enabled server without enabling unsupported simulate integrations', () => { + const cmake = source('src/server/CMakeLists.txt'); + const tests = source('tests/server/CMakeLists.txt'); + for (const family of families.filter(name => name !== 'simulate')) { + assert.match(cmake, new RegExp(`^add_subdirectory\\(${family}\\)`, 'm')); + assert.match(tests, new RegExp(`\\b${family}\\b`)); + assert.equal(source(`src/server/${family}/CMakeLists.txt`).trim(), `BUILD_SERVER_MACRO(ark_${family}_plugin)`); + } + assert.doesNotMatch(cmake, /^add_subdirectory\(simulate\)/m); + assert.match(source('src/server/simulate/CMakeLists.txt'), /message\(FATAL_ERROR/); + assert.match(source('tests/core/CMakeLists.txt'), /add_dependencies\(check-core check-server-plugins\)/); +}); + +test('DSO export inspection stays in a core-only process separate from the real-factory host', () => { + const cmake = source('tests/server/CMakeLists.txt'); + const construction = source('tests/server/construction.cpp'); + const exports = source('tests/server/exports.cpp'); + assert.match(cmake, /add_executable\(ark_server_exports_tests exports\.cpp\)/); + // Replace inherited aggregate linkage rather than appending core to ark::ark: + // the export host must not register static generated Protobuf descriptors. + assert.match(cmake, /set_property\(TARGET ark_server_exports_tests PROPERTY LINK_LIBRARIES ark::core\)/); + assert.match(cmake, /COMMAND \$ "\$\/" "ark_\$\{server\}_plugin"\s+COMMAND \$ "ark_\$\{server\}_plugin"/); + assert.doesNotMatch(construction, /dynamic_library|\.load\(|get_symbol\(/); + assert.doesNotMatch(exports, /server_test_types|ark\/proto|_module_factory|_module_impl/); + for (const entry of ['DllEntryPlugin', 'DllExitPlugin']) { + assert.ok(exports.includes(`get_symbol("${entry}")`)); + } + // Keep the actual manager-loader path with the controlled Login factory. + assert.match(source('tests/server/entry.cpp'), /manager\.start\(/); + assert.match(source('tests/server/entry.cpp'), /manager\.stop\(/); +}); diff --git a/tests/core/CMakeLists.txt b/tests/core/CMakeLists.txt index 2d66fbe8e..20a4555e0 100644 --- a/tests/core/CMakeLists.txt +++ b/tests/core/CMakeLists.txt @@ -287,3 +287,7 @@ if(TARGET ark_http_plugin) USES_TERMINAL VERBATIM) add_dependencies(check-core check-http-plugin) endif() + +if(TARGET check-server-plugins) + add_dependencies(check-core check-server-plugins) +endif() diff --git a/tests/server/CMakeLists.txt b/tests/server/CMakeLists.txt new file mode 100644 index 000000000..c42e7f968 --- /dev/null +++ b/tests/server/CMakeLists.txt @@ -0,0 +1,50 @@ +# Copyright (c) 2026 OpenArkStudio. SPDX-License-Identifier: Apache-2.0 +# Keep this host free of the aggregate SDK and generated protocol objects. Each +# DSO inspection runs in its own process, separate from the real-factory host, +# so static AFProto descriptors cannot register twice in one process. +add_executable(ark_server_exports_tests exports.cpp) +set_property(TARGET ark_server_exports_tests PROPERTY LINK_LIBRARIES ark::core) + +add_custom_target(check-server-plugins) +foreach(server game login proxy world master dir router) + set(source "${PROJECT_SOURCE_DIR}/src/server/${server}") + configure_file(types.hpp.in "${CMAKE_CURRENT_BINARY_DIR}/${server}/server_test_types.hpp" @ONLY) + set(test_target ark_${server}_construction_tests) + # Compile the real private factory and assembly into the test as well: Windows + # does not export those C++ internals from the runtime DLL. + add_executable(${test_target} construction.cpp + "${source}/${server}_plugin.cpp" "${source}/${server}_net_module_impl.cpp") + set_property(TARGET ${test_target} PROPERTY LINK_LIBRARIES ark::ark ark_server_compat_headers) + target_include_directories(${test_target} PRIVATE "${source}" "${CMAKE_CURRENT_BINARY_DIR}/${server}") + add_dependencies(${test_target} ark_${server}_plugin) + add_custom_target(check-server-${server} + COMMAND $ "$/" "ark_${server}_plugin" + COMMAND $ "ark_${server}_plugin" + DEPENDS ark_server_exports_tests ${test_target} ark_${server}_plugin USES_TERMINAL VERBATIM) + add_dependencies(check-server-plugins check-server-${server}) +endforeach() + +# A counted substitute verifies rejected-factory ownership through the actual +# Login assembly. This does not replace the real-factory tests above. +add_executable(ark_server_registration_tests registration.cpp + "${PROJECT_SOURCE_DIR}/src/server/login/login_plugin.cpp") +set_property(TARGET ark_server_registration_tests PROPERTY LINK_LIBRARIES ark::ark ark_server_compat_headers) +target_include_directories(ark_server_registration_tests PRIVATE "${PROJECT_SOURCE_DIR}/src/server/login") +add_custom_target(check-server-registration + COMMAND $ + DEPENDS ark_server_registration_tests USES_TERMINAL) +add_dependencies(check-server-plugins check-server-registration) + +# Exercise the actual Login entry/exit functions through the real manager loader +# with a controlled module, without invoking business network initialization. +add_library(ark_server_entry_probe SHARED entry_factory.cpp + "${PROJECT_SOURCE_DIR}/src/server/login/login_plugin.cpp") +set_property(TARGET ark_server_entry_probe PROPERTY LINK_LIBRARIES ark::ark) +target_include_directories(ark_server_entry_probe PRIVATE "${PROJECT_SOURCE_DIR}/src/server/login") +set_target_properties(ark_server_entry_probe PROPERTIES PREFIX "") +add_executable(ark_server_entry_tests entry.cpp) +set_property(TARGET ark_server_entry_tests PROPERTY LINK_LIBRARIES ark::ark ark_server_compat_headers) +add_custom_target(check-server-entry + COMMAND $ "$/" "ark_server_entry_probe" + DEPENDS ark_server_entry_tests ark_server_entry_probe USES_TERMINAL VERBATIM) +add_dependencies(check-server-plugins check-server-entry) diff --git a/tests/server/construction.cpp b/tests/server/construction.cpp new file mode 100644 index 000000000..2e47d832c --- /dev/null +++ b/tests/server/construction.cpp @@ -0,0 +1,71 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include +#include +#include +#include +#include +#include + +#include +#include +#include "server_test_types.hpp" + +static_assert(std::derived_from>); +static_assert(std::derived_from>); +static_assert(std::same_as); + +namespace { + +void require(bool condition, const std::string& message) { + if (!condition) + { + std::cerr << message << std::endl; + std::_Exit(1); + } +} + +} // namespace + +int main(int argc, char* argv[]) { + require(argc == 2, "Expected server library stem"); + ark::logger log; + ark::logger::init(&log); + log.set_log_func([](spdlog::level::level_enum, const std::string&) {}); + + auto candidate = make_server_module(); + require( + candidate && typeid(*candidate) == typeid(server_implementation), "Factory returned the wrong implementation"); + require(candidate->get_plugin_manager() == nullptr && candidate->get_name().empty(), + "Factory published metadata early"); + candidate.reset(); + + ark::plugin_manager manager; + server_plugin owner; + owner.set_plugin_manager(&manager); + require(owner.get_plugin_version() == 0 && owner.get_plugin_name() == typeid(server_plugin).name(), + "Plugin version or RTTI name changed"); + owner.set_plugin_conf("server-test.conf"); + require(server_plugin::get_plugin_conf() == "server-test.conf", "Plugin configuration storage changed"); + owner.install(); + auto* instance = manager.find_module(); + require(instance && typeid(*instance) == typeid(server_implementation), "Assembly registered the wrong factory"); + require(instance->get_plugin_manager() == &manager && instance->get_name() == typeid(server_module).name(), + "Assembly lost manager ownership or interface RTTI key"); + // The existing admission seam reports whether the module was already in the + // update set. Remove this observation immediately to preserve disabled mode. + require(manager.add_update_module(instance), "Server module was unexpectedly admitted to updates"); + manager.remove_update_module(instance->get_name()); + bool rejected = false; + try + { owner.install(); } + catch (const std::runtime_error&) + { rejected = true; } + require( + rejected && manager.find_module() == instance, "Duplicate install replaced the owned module"); + owner.uninstall(); + // Successful reinstallation below proves the public registry released the key. + owner.install(); + owner.uninstall(); + require(manager.update(1), "Uninstall left invalid update bookkeeping"); + std::cout << argv[1] << ": actual factory, interface key, disabled update, duplicate rejection, reinstall passed\n"; +} diff --git a/tests/server/entry.cpp b/tests/server/entry.cpp new file mode 100644 index 000000000..4c2f324fa --- /dev/null +++ b/tests/server/entry.cpp @@ -0,0 +1,44 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include +#include +#include +#include + +#include +#include +#include "login/interface/login_net_module.hpp" + +namespace { + +void require(bool condition, const char* message) { + if (!condition) + { + std::cerr << message << std::endl; + std::_Exit(1); + } +} + +} // namespace + +int main(int argc, char* argv[]) { + require(argc == 3, "Expected entry-probe library directory and stem"); + ark::logger log; + ark::logger::init(&log); + log.set_log_func([](spdlog::level::level_enum, const std::string&) {}); + ark::dynamic_library library(argv[2]); + require(library.load(argv[1]), "Cannot load entry-probe library"); + auto events = reinterpret_cast(library.get_symbol("server_entry_events")); + require(events && events(0) == 0 && events(1) == 0, "Entry-probe state is not fresh"); + ark::plugin_manager manager; + require(manager.start(0, argv[1], "conf/", {{argv[2], "entry-probe.conf"}}), "Explicit Login entry failed"); + auto* instance = manager.find_module(); + require(instance && instance->get_plugin_manager() == &manager && + instance->get_name() == typeid(ark::login_net_module).name(), + "Explicit entry did not publish the Login interface"); + require(events(0) == 1 && events(1) == 0, "Explicit entry did not transfer factory ownership"); + require(manager.update(1) && events(2) == 0, "Explicit entry changed disabled update admission"); + require(manager.stop(2) && events(1) == 1, "Explicit Login exit did not destroy its owned module"); + require(manager.stop(3) && events(1) == 1, "Repeated stop invoked the exit path twice"); + require(library.unload(), "Cannot release entry-probe observation handle"); + std::cout << "server entry: real Login assembly entry/exit with a controlled factory passed\n"; +} diff --git a/tests/server/entry_factory.cpp b/tests/server/entry_factory.cpp new file mode 100644 index 000000000..0fdb9769c --- /dev/null +++ b/tests/server/entry_factory.cpp @@ -0,0 +1,50 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include + +#include +#include "login_module_factory.hpp" + +namespace { + +unsigned created{}; +unsigned destroyed{}; +unsigned updated{}; + +class entry_login final : public ark::module_base +{ +public: + entry_login() { + ++created; + } + + ~entry_login() override { + ++destroyed; + } + + bool update() override { + ++updated; + return true; + } +}; + +} // namespace + +namespace ark::detail { + +std::unique_ptr make_login_net_module() { + return std::make_unique(); +} + +} // namespace ark::detail + +ARK_EXPORT_FUNC unsigned server_entry_events(unsigned event) { + switch (event) + { + case 0: + return created; + case 1: + return destroyed; + default: + return updated; + } +} diff --git a/tests/server/exports.cpp b/tests/server/exports.cpp new file mode 100644 index 000000000..58b4e45ce --- /dev/null +++ b/tests/server/exports.cpp @@ -0,0 +1,31 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include +#include +#include + +#include + +namespace { + +void require(bool condition, const char* message) { + if (!condition) + { + std::cerr << message << std::endl; + std::_Exit(1); + } +} + +} // namespace + +int main(int argc, char* argv[]) { + require(argc == 3, "Expected plugin directory and library stem"); + ark::logger log; + ark::logger::init(&log); + log.set_log_func([](spdlog::level::level_enum, const std::string&) {}); + ark::dynamic_library library(argv[2]); + require(library.load(argv[1]), "Cannot load actual server shared library"); + require(library.get_symbol("DllEntryPlugin") != nullptr, "Missing explicit DllEntryPlugin export"); + require(library.get_symbol("DllExitPlugin") != nullptr, "Missing explicit DllExitPlugin export"); + require(library.unload(), "Cannot unload server shared library"); + std::cout << argv[2] << ": actual DSO load, explicit entry/exit exports and unload passed\n"; +} diff --git a/tests/server/registration.cpp b/tests/server/registration.cpp new file mode 100644 index 000000000..d7e060b99 --- /dev/null +++ b/tests/server/registration.cpp @@ -0,0 +1,86 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include +#include +#include +#include +#include + +#include +#include "login/include/login_plugin.hpp" +#include "login/interface/login_net_module.hpp" +#include "login_module_factory.hpp" + +namespace { + +unsigned created{}; +unsigned destroyed{}; +unsigned updated{}; + +class counted_login final : public ark::module_base +{ +public: + counted_login() { + ++created; + } + + ~counted_login() override { + ++destroyed; + } + + bool update() override { + ++updated; + return true; + } +}; + +void require(bool condition, const char* message) { + if (!condition) + { + std::cerr << message << std::endl; + std::_Exit(1); + } +} + +bool rejects(ark::login_plugin& owner) { + try + { owner.install(); } + catch (const std::runtime_error&) + { return true; } + return false; +} + +} // namespace + +namespace ark::detail { + +std::unique_ptr make_login_net_module() { + return std::make_unique(); +} + +} // namespace ark::detail + +int main() { + ark::logger log; + ark::logger::init(&log); + log.set_log_func([](spdlog::level::level_enum, const std::string&) {}); + ark::plugin_manager manager; + ark::login_plugin owner; + ark::login_plugin competitor; + owner.set_plugin_manager(&manager); + competitor.set_plugin_manager(&manager); + owner.install(); + auto* original = manager.find_module(); + require(original && created == 1 && destroyed == 0, "Initial factory ownership failed"); + require(rejects(owner) && created == 2 && destroyed == 1, "Duplicate candidate was not destroyed exactly once"); + require(rejects(competitor) && created == 3 && destroyed == 2, "Manager-collision candidate leaked"); + require(manager.find_module() == original, "Rejected install changed the original module"); + require(manager.update(1) && updated == 0, "Actual Login assembly enabled update for its substitute factory"); + owner.uninstall(); + require(destroyed == 3, "Owned module cleanup failed"); + competitor.install(); + require(created == 4 && destroyed == 3, "Failed install retained stale per-plugin bookkeeping"); + competitor.uninstall(); + require( + destroyed == 4 && manager.update(2) && updated == 0, "Reinstalled substitute cleanup or update mode failed"); + std::cout << "server registration: rejected candidates, original ownership, retry and disabled updates passed\n"; +} diff --git a/tests/server/types.hpp.in b/tests/server/types.hpp.in new file mode 100644 index 000000000..619e03600 --- /dev/null +++ b/tests/server/types.hpp.in @@ -0,0 +1,16 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#pragma once + +// Exercise every retained source-consumer include category. +#include "@server@/include/@server@_plugin.hpp" +#include "@server@/include/@server@_net_module_impl.hpp" +#include "@server@/interface/@server@_net_module.hpp" +#include "@server@_module_factory.hpp" + +using server_plugin = ark::@server@_plugin; +using server_module = ark::@server@_net_module; +using server_implementation = ark::@server@_net_module_impl; + +inline std::unique_ptr make_server_module() { + return ark::detail::make_@server@_net_module(); +}