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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 80 additions & 37 deletions docs/guides/modules.md
Original file line number Diff line number Diff line change
@@ -1,76 +1,119 @@
# Create a module

Modules expose services through public interfaces and are registered by plugins.
A module derives from `ark::module`; its `update()` method participates in the application loop.
A module exposes an application service through a C++ interface. A plugin creates and registers its implementation; the plugin manager invokes its lifecycle callbacks. Use modules for services such as entity access, timers or application rules. Use a plugin to package and install a group of modules.

## Define a module
This example separates a counter interface from its implementation and calls it from a second module. Pair it with [Create a plugin](plugins.md) to build the shared library.

## Define the interface and implementation

Save this file as `sample_module.hpp`:

```cpp
#pragma once

#include <cstddef>
#include <cstdint>

#include <ark/module.hpp>
#include <ark/plugin_manager.hpp>

namespace ark {

class counter_module : public module
{
public:
virtual void increment() = 0;
virtual std::uint64_t value() const = 0;
};

class counter_module_impl final : public counter_module
{
ARK_DECLARE_MODULE_FUNCTIONS

public:
void increment() override {
++value_;
}

std::uint64_t value() const override {
return value_;
}

private:
std::uint64_t value_ = 0;
};

class sample_module final : public module
{
ARK_DECLARE_MODULE_FUNCTIONS

public:
bool init() override {
update_count_ = 0;
return true;
bool post_init() override {
counter_module_ = find_module<counter_module>();
return counter_module_ != nullptr;
}

bool update() override {
++update_count_;
if (counter_module_ == nullptr)
{
return false;
}

counter_module_->increment();
return true;
}

bool pre_shut() override {
counter_module_ = nullptr;
return true;
}

private:
std::size_t update_count_ = 0;
counter_module* counter_module_ = nullptr;
};

} // namespace ark
```

`ARK_DECLARE_MODULE_FUNCTIONS` supplies the module name and manager accessors.
The registration macro assigns both. Modules use `find_module<interface_type>()`
to retrieve other registered modules. The counter illustrates frame updates.
The example keeps the three types in one file so their relationship is visible. In an application, share the interface header with consumers and keep implementation details private. Both implementations use `ARK_DECLARE_MODULE_FUNCTIONS`, which stores the manager pointer and registered name and supplies the protected `find_module<T>()` helper.

## Register and find the same interface

## Lifecycle callbacks
Register `counter_module_impl` under `counter_module`, then retrieve it with `find_module<counter_module>()`. The registration key comes from the interface type's RTTI name; an implementation name or a shared-library filename is a different key. Register one implementation per interface in a manager. The macros do not turn duplicate registration into a supported multi-provider model.

| Callback | Purpose |
| --- | --- |
| `init()` | Initialize module resources |
| `post_init()` | Perform work that depends on other modules being initialized |
| `check_config()` | Validate module configuration |
| `pre_update()` | Prepare for frame updates |
| `update()` | Run in the application update loop |
| `pre_shut()` | Begin module shutdown |
| `shut()` | Release module resources |
The returned pointer is borrowed. Do not delete it or retain it after the owning plugin unregisters the module. A missing interface or failed dynamic cast triggers an assertion when assertions are enabled; with assertions disabled, `find_module<T>()` returns `nullptr`. Check the result before use, and do not use a failed lookup as an optional-service probe in Debug. Build consumers and plugins with compatible headers, compiler/ABI settings and RTTI enabled.

The two example modules share the application update thread. The counter is deliberately not atomic; it does not support calls from arbitrary worker threads. Queue worker completions for the owning module thread or use a service's documented asynchronous API. See the [network reference](../reference/network.md) for network callback dispatch.

## Choose lifecycle stages

| Callback | Appropriate work | Current boundary |
| --- | --- | --- |
| `init()` | Initialize local resources | Other registered modules may not have run `init()` yet |
| `post_init()` | Resolve interfaces and use dependencies initialized in `init()` | Other modules' `post_init()` ordering is not a dependency order |
| `check_config()` | Check configuration and dependencies | This callback's Boolean result is not currently aggregated |
| `pre_update()` | Prepare work before the application loop | Keep this stage bounded |
| `update()` | Process queued work for a frame | Called only for implementations that override it |
| `pre_shut()` | Stop new work and begin releasing subscriptions | All modules enter this stage before `shut()` |
| `shut()` | Release module-owned resources | Library unloading follows the shutdown callbacks |

![Module lifecycle](../assets/module-lifecycle.svg)

The base class supplies implementations returning `true`. Override only the
stages a module needs. See [plugin_manager](https://github.com/OpenArkStudio/ark/blob/main/include/ark/plugin_manager.hpp)
for the current invocation and error handling behavior. The diagram describes
the existing startup, update and shutdown flow.
All module libraries are installed before the manager starts the initialization stages. Each stage visits the registered modules; the current containers do not provide dependency sorting or manifest-order execution. The base callbacks return `true`; override only the stages needed. The registration macro adds an implementation to the update list when it overrides `update()`. The counter provider therefore receives no frame updates, while `sample_module` increments it once per manager update.

<Aside type="caution" title="Callback results do not abort startup">

The current manager discards the Boolean values returned by module lifecycle callbacks, including `post_init()` and `update()`. With assertions disabled, the example's null check prevents dereferencing a failed lookup; returning `false` does not make the application reject startup. Keep the runtime plugin selection complete and report configuration failures explicitly.

</Aside>

## Keep callbacks within their owner's lifetime

## Register and access modules
The example only holds a borrowed pointer and clears it in `pre_shut()`. A real module may also register timers, event handlers or asynchronous operations that capture `this`. Record their identifiers or cancellation handles, stop accepting new work, and unregister or cancel them according to the owning service's API before destroying the module. Cancellation alone must not be assumed to mean that every completion has already run.

A plugin registers a module with `ARK_REGISTER_MODULE(interface_type, implementation_type)`
in `install()` and releases it with `ARK_UNREGISTER_MODULE(interface_type, implementation_type)`
in `uninstall()`. Pair the two operations as shown in [Create a plugin](plugins.md).
The manager does not track arbitrary callbacks or prove that all work using a plugin has finished. Do not infer safe library replacement or a complete callback-drain guarantee from `pre_shut()`, `shut()` or a successful `stop()` return. Hot-update behavior is outside this guide.

The manager exposes `find_module<interface_type>()`. Access module names with
`get_name()` and `set_name()`, and their manager with `get_plugin_manager()` and
`set_plugin_manager()`. Public interfaces do not require the concrete implementation;
any retained module pointer needs a defined lifetime.
## Continue

arknet executes network IO and transport operations. The ark network plugin
queues application messages and events, then dispatches business callbacks in
`update()`. See the [network reference](../reference/network.md) for its constraints.
- [Create a plugin](plugins.md): registration pairs, DLL entry points, build and runtime selection.
- [Application launcher](applications.md): process arguments and configuration paths.
- [Kernel reference](../reference/kernel.md): entity and data services exposed by official modules.
114 changes: 80 additions & 34 deletions docs/guides/modules_CN.md
Original file line number Diff line number Diff line change
@@ -1,73 +1,119 @@
# 创建模块

模块通过公开接口提供服务,由插件注册和卸载。类型继承 `ark::module`;
应用循环通过 `update()` 分发需要逐帧处理的工作。
模块通过 C++ 接口提供应用服务,插件负责创建和注册实现,插件管理器调用生命周期回调。实体访问、定时器和业务规则都可以作为模块;插件用于组合并安装一组模块。

## 定义模块
下面将计数器接口与实现分开,并由另一个模块调用。配合[创建插件](plugins_CN.md)中的示例即可构建动态库。

## 定义接口与实现

将下面的代码保存为 `sample_module.hpp`:

```cpp
#pragma once

#include <cstddef>
#include <cstdint>

#include <ark/module.hpp>
#include <ark/plugin_manager.hpp>

namespace ark {

class counter_module : public module
{
public:
virtual void increment() = 0;
virtual std::uint64_t value() const = 0;
};

class counter_module_impl final : public counter_module
{
ARK_DECLARE_MODULE_FUNCTIONS

public:
void increment() override {
++value_;
}

std::uint64_t value() const override {
return value_;
}

private:
std::uint64_t value_ = 0;
};

class sample_module final : public module
{
ARK_DECLARE_MODULE_FUNCTIONS

public:
bool init() override {
update_count_ = 0;
return true;
bool post_init() override {
counter_module_ = find_module<counter_module>();
return counter_module_ != nullptr;
}

bool update() override {
++update_count_;
if (counter_module_ == nullptr)
{
return false;
}

counter_module_->increment();
return true;
}

bool pre_shut() override {
counter_module_ = nullptr;
return true;
}

private:
std::size_t update_count_ = 0;
counter_module* counter_module_ = nullptr;
};

} // namespace ark
```

`ARK_DECLARE_MODULE_FUNCTIONS` 提供模块名称和管理器访问方法。
注册宏设置管理器与模块名称;模块通过 `find_module<接口类型>()` 获取其他已注册模块。
示例中的计数器展示逐帧更新。
示例把三个类型放在同一个文件中,便于说明关系。应用项目应向调用方提供接口头文件,将实现细节保留在内部。两个实现类都使用 `ARK_DECLARE_MODULE_FUNCTIONS`;这个宏保存管理器指针和注册名称,并提供受保护的 `find_module<T>()` 方法。

## 使用同一个接口注册和查找

## 生命周期回调
以 `counter_module` 为接口注册 `counter_module_impl`,再通过 `find_module<counter_module>()` 查找。注册键来自接口类型的 RTTI 名称;实现类名称和动态库文件名都是不同的键。同一个管理器中,每个接口只注册一个实现。注册宏不会把重复注册转换成受支持的多实现模型。

| 回调 | 用途 |
| --- | --- |
| `init()` | 初始化模块资源 |
| `post_init()` | 执行依赖其他模块初始化结果的工作 |
| `check_config()` | 检查模块配置 |
| `pre_update()` | 启动逐帧更新前的准备 |
| `update()` | 在应用更新循环中执行 |
| `pre_shut()` | 开始关闭模块 |
| `shut()` | 释放模块资源 |
返回的指针由插件持有,调用方不拥有对象。不要删除这个指针,也不要在插件注销模块后继续使用。接口不存在或动态类型转换失败时,启用断言的构建会触发断言;关闭断言时,`find_module<T>()` 返回 `nullptr`。使用前应检查返回值,不要在 Debug 中通过失败查找探测可选服务。应用和插件需要使用兼容的头文件、编译器与 ABI 设置,并启用 RTTI。

示例中的两个模块在应用更新线程中调用。计数器没有使用原子变量,不支持任意工作线程直接访问。工作线程的完成结果应投递到模块所属线程,或使用服务明确提供的异步 API。网络回调的分发方式见[网络接口](../reference/network_CN.md)。

## 选择生命周期阶段

| 回调 | 适合处理的工作 | 当前约束 |
| --- | --- | --- |
| `init()` | 初始化本模块资源 | 其他已注册模块可能尚未执行 `init()` |
| `post_init()` | 查找接口,使用已在 `init()` 中初始化的依赖 | 其他模块的 `post_init()` 不按依赖顺序执行 |
| `check_config()` | 检查配置和依赖 | 管理器目前不汇总回调的布尔返回值 |
| `pre_update()` | 准备进入应用循环 | 避免在这个阶段执行无期限的工作 |
| `update()` | 处理当前帧的队列任务 | 只调用实现类覆盖的 `update()` |
| `pre_shut()` | 停止接收新任务,开始解除订阅 | 所有模块先完成这个阶段,再进入 `shut()` |
| `shut()` | 释放模块持有的资源 | 完成关闭回调后才卸载动态库 |

![模块生命周期](../assets/module-lifecycle.svg)

基类为上述回调提供返回 `true` 的默认实现。只覆盖模块需要的阶段;
具体调用和错误处理以 [plugin_manager](https://github.com/OpenArkStudio/ark/blob/main/include/ark/plugin_manager.hpp) 为准。
这张图展示现有应用的启动、更新和关闭流程。
管理器先安装所有模块,再执行初始化阶段。每个阶段遍历已注册模块;当前容器不提供依赖排序,也不保证按照清单顺序执行。基类回调默认返回 `true`,只需覆盖使用到的阶段。注册宏检测到实现类覆盖 `update()` 时,才把它加入更新列表。因此计数器实现不参与逐帧更新,`sample_module` 则在每次管理器更新时递增一次计数器。

<Aside type="caution" title="回调返回值不会中止启动">

当前管理器忽略模块生命周期回调的布尔返回值,包括 `post_init()` 和 `update()`。关闭断言时,示例的空指针检查可以避免访问缺失的依赖;返回 `false` 不会让应用拒绝启动。部署时应完整配置依赖插件,并显式报告配置错误。

</Aside>

## 保证回调不超出对象生命周期

## 注册与访问
示例只保存其他模块的指针,并在 `pre_shut()` 中清空。实际模块还可能注册定时器、事件处理器或捕获 `this` 的异步操作。保存这些操作的标识符或取消句柄,先停止接收新任务,再按所属服务的 API 注销或取消,最后才销毁模块。不能仅凭取消操作推断所有完成回调都已执行。

插件在 `install()` 中使用 `ARK_REGISTER_MODULE(接口类型, 实现类型)` 注册模块,
在 `uninstall()` 中使用 `ARK_UNREGISTER_MODULE(接口类型, 实现类型)` 释放模块。
两者必须对应,见[创建插件](plugins_CN.md)。
管理器不会追踪任意回调,也不会证明所有使用插件的任务都已结束。不能根据 `pre_shut()`、`shut()` 或 `stop()` 返回成功,推断动态库可以安全替换,或所有回调都已排空。热更新不在本文范围内。

管理器提供 `find_module<接口类型>()`。模块名称由 `get_name()`、`set_name()` 访问;
所属管理器由 `get_plugin_manager()`、`set_plugin_manager()` 访问。
公开接口不依赖具体实现类,成员持有其他模块指针时,应明确其有效期。
## 继续阅读

网络 IO 和传输由 arknet 执行。ark 网络插件把业务消息与事件放入队列,
在 `update()` 中分发给业务回调;相关约束见[网络接口](../reference/network_CN.md)。
- [创建插件](plugins_CN.md):配对注册、动态库入口、构建与运行选择。
- [应用启动器](applications_CN.md):进程参数和配置路径。
- [Kernel 接口](../reference/kernel_CN.md):官方模块提供的实体与数据服务。
Loading
Loading