core/shell/README.md
The "shell" module is located in lynx/core/shell and implements the
multi-threaded architecture of Lynx. It manages the native-layer lifecycle,
bridges interactions between the native layer and the platform layer, and
handles cross-thread communication in a unified manner.
The diagram below illustrates the hierarchical relationship of the shell module within Lynx.
+---------------------------------------------------------------------------+
| LynxView (Platform Layer) |
+--------------------------------------^------------------------------------+
|
v
+---------------------------------------------------------------------------+
| LynxShell (Shell Layer) |
| (Manages lifecycle, cross-thread comm, etc.) |
+----------+---------------+------------------+-------------------+---------+
| | | |
v v v v
+----------------+ +---------------+ +----------------+ +----------------+
| NativeFacade | | LynxEngine | | LayoutContext | | LynxRuntime |
| (Platform- | | (Tasm-thread | | (Layout-thread | | (JS-thread |
| thread Actor) | | Actor) | | Actor) | | Actor) |
+------^---------+ +------^--------+ +------^---------+ +------^---------+
| | |
v v v
+---------------+ +----------------+ +-----------------+
| TasmMediator | | LayoutMediator | | RuntimeMediator |
| | | | | |
| | | | | |
+------^--------+ +------^---------+ +------^----------+
All inter-thread communication occurs via delegates or message passing. Direct state sharing or synchronous access should be restricted.
LynxActor is the core abstraction in LynxShell’s multi-threaded design. Each actor:
By separating functionality into distinct LynxActor units, the shell module achieves clearer boundaries, better scalability, and improved maintainability.
Ensure that your module’s lifecycle is managed by a LynxActor. This means that your module needs to be a compositional part of LynxActor.
std::unique_ptr in the parent object for ownership.std::shared_ptr for lifecycle management.Thread-to-Actor Correspondences:
All cross-thread communication in the native layer is achieved through LynxActor. The caller does not need to worry about lifecycle details—just use LynxActor::Act, which takes a closure and ensures proper execution on the corresponding thread.
Define a Delegate for cross-thread communication:
class YourModule {
public:
class Delegate {
public:
Delegate() = default;
virtual ~Delegate() = default;
virtual void CallFunction() = 0;
};
};
Let the Delegate inherit from LynxEngine::Delegate and implement the method in TasmMediator:
class LynxEngine {
public:
class Delegate : public YourModule::Delegate {
};
};
class TasmMediator : public LynxEngine::Delegate {
public:
void CallFunction() override;
};
Access the desired LynxActor within TasmMediator:
void TasmMediator::CallFunction() {
runtime_actor_->Act(
[](auto& runtime) {
runtime->CallFunction();
});
}
Ensure LynxShell is accessed on the platform UI thread. Access from background threads may cause crashes. For platform-layer access to the native layer on background threads, refer to the next section.
To call CallFunction through the platform layer on the TASM thread:
void LynxShell::CallFunction() {
engine_actor_->Act(
[](auto& engine) {
engine->CallFunction();
});
}
When the native layer needs to be accessed from a background thread, use proxies like LynxEngineProxy or JSProxy.
For example:
class LynxEngineProxy {
public:
virtual void CallFunction() = 0;
};
Implement the method in LynxEngineProxyImpl:
class LynxEngineProxyImpl : public LynxEngineProxy {
public:
void CallFunction() override {
engine_actor_->Act(
[](auto& engine) {
engine->CallFunction();
});
}
};