简体中文 English 繁體中文 日本語 Русский
本文为静态镜像,内容以交互版为准 在交互式文档中心打开 →

Lazy-Loaded Module System

The ErisPulse SDK provides a powerful lazy-loaded module system that allows modules to be initialized only when they are actually needed, significantly improving application startup speed and memory efficiency.

Overview

The lazy-loaded module system is one of the core features of ErisPulse, which works as follows:

Working Principle

LazyModule Class

The core of the lazy loading system is the LazyModule class, which acts as a wrapper that actually initializes the module only when it is first accessed.

Initialization Process

When a module is first accessed, LazyModule performs the following operations:

  1. Retrieves the __init__ parameter information of the module class.
  2. Determines whether to pass the sdk reference based on the parameters.
  3. Sets the moduleInfo attribute of the module.
  4. For modules that inherit from BaseModule, calls the on_load method.
  5. Triggers the module.init lifecycle event.

Event-Driven Lazy Activation (activate_on)

Note

This feature requires ErisPulse 2.8.0+.

Modules with lazy_load=True are loaded only on their first attribute access by default. If a module registers command/event handlers, the traditional approach would require lazy_load=False to load immediately. activate_on provides a third option: declare triggers, and the module activates automatically when the first matching event/command arrives—neither staying in memory constantly nor losing the trigger entry.

from ErisPulse.loaders import ModuleLoadStrategy

class MyModule(BaseModule):
    @staticmethod
    def get_load_strategy():
        return ModuleLoadStrategy(
            lazy_load=True,
            activate_on=[
                # ---- Event triggers (passive arrival, no user awareness) ----
                "message",                                    # Type-level: any message event
                {"notice": "group_member_increase"},          # Type + single detail_type
                {"message": ["private", "group"]},            # Type + multiple detail_types

                # ---- Command triggers (active input, placeholder commands visible in Help) ----
                {"command": "roll"},                          # Shorthand: command name
                {"command": ["roll", "dice"]},                # List of command names
                {"command": {                                 # Dict declaration (name required)
                    "name": "dice",
                    "help": "Roll a dice",
                    "usage": "/dice",
                    "group": "Entertainment",
                    "aliases": ["d"],
                    "hidden": False,
                }},
            ],
        )

Command Dict Declaration Parameters

The dict form mirrors the user-level parameters of the @command() decorator, used to register placeholder commands before module loading:

Parameter Type Default Description
name str Required Command name; must match @command(name) in on_load, otherwise the placeholder is unregistered after activation, and the command becomes unavailable
help str Fallback chain Description shown in Help; if not declared, falls back to the chain (see below)
usage str Auto-generated Usage line, defaulting to {prefix}{name}
group str None Command group
aliases list[str] [] Aliases are registered simultaneously; inputting an alias also triggers activation
hidden bool False If True, the placeholder command is hidden (aligned with the hidden semantics of the activated real command); users who know the command name can still trigger activation

Not supported: priority / permission / master: The placeholder command's role is only to trigger activation. Permission checks are performed by the real command after activation (blocking permissions at the placeholder stage would make "activating on command input" ineffective).

Placeholder Command Help Fallback Chain

When the module is not loaded, the Help displays command descriptions according to the following priority (first match wins):

  1. The command-level help declared in the dict (most precise)
  2. The module's get_meta() description
  3. The module's __description__ attribute
  4. The package metadata's Summary (PyPI package summary)
  5. Generic prompt: "This command comes from a lazy-loaded module X. The module will be automatically loaded on first use."

Trigger Semantics

For architecture diagrams and full semantics, see Architecture Overview.

Configuring Lazy Loading

Global Configuration

Enable or disable global lazy loading in the configuration file:

[ErisPulse.framework]
enable_lazy_loading = true  # true=enable lazy loading (default), false=disable lazy loading

Module-Level Control

Modules can control their loading strategy by implementing the get_load_strategy() static method:

from ErisPulse.Core.Bases import BaseModule
from ErisPulse.loaders import ModuleLoadStrategy

class MyModule(BaseModule):
    @staticmethod
    def get_load_strategy():
        """Returns the module loading strategy"""
        return ModuleLoadStrategy(
            lazy_load=False,  # Return False to indicate immediate loading
            priority=100      # Loading priority, higher value means higher priority
        )

Using Lazy-Loaded Modules

Basic Usage

For developers, lazy-loaded modules are almost indistinguishable from regular modules in usage:

# Accessing a lazy-loaded module through the SDK
from ErisPulse import sdk

# The following access triggers the module's lazy loading
result = await sdk.my_module.my_method()

Unified Module Access Entry

Whether accessed through SDK attributes, module manager attributes, or via module.get(), for "registered but not yet loaded" lazy-loaded modules, the same lazy-loading proxy is returned. Accessing its attributes triggers the actual initialization:

# All three methods return the same lazy-loading proxy (when the module is not loaded), behaving consistently and transparently to the user
sdk.my_module          # Entry point that triggers loading
sdk.module.my_module   # Also returns the lazy-loading proxy
sdk.module.get("my_module")  # Also returns the lazy-loading proxy, itself does not trigger loading

# Accessing any attribute of the proxy triggers the actual initialization of the module
result = await sdk.my_module.my_method()

module.get() is a query interface and does not trigger loading by itself:

To explicitly trigger loading, use await sdk.load_module("my_module").

Asynchronous Initialization

For modules requiring asynchronous initialization, it is recommended to load them explicitly first:

# Explicitly load the module first
await sdk.load_module("my_module")

# Then use the module
result = await sdk.my_module.my_method()

Synchronous Initialization

For modules that do not require asynchronous initialization, you can access them directly:

# Direct access will automatically initialize synchronously
result = sdk.my_module.some_sync_method()

Best Practices

When choosing a loading strategy, you can refer to the following decision flow:

flowchart TD
    A["Module Declaration<br/>get_load_strategy()"] --> B{"Do you need it ready at startup<br/>or frequently triggered?"}
    B -->|"Yes"| C["lazy_load=False<br/>Load Immediately"]
    B -->|"No"| D{"Registered Command / Event Handlers?"}
    D -->|"Yes"| E["lazy_load=True + activate_on<br/>Activate when event/command arrives"]
    D -->|"No"| F["lazy_load=True<br/>Load on first attribute access"]
    C --> G["Call on_load() at startup"]
    E --> H["Register stub → Instantiate on trigger"]
    F --> I["LazyModule Proxy"]

The priority parameter controls the initialization order of immediately loaded modules; higher values are initialized first. Modules with the same priority are loaded in registration order.

Notes

  1. If your module uses lazy loading, and other modules have never been called within ErisPulse, your module will never be initialized.
  2. If your module contains modules that listen for Events, or other actively listening modules, you have two options: declare an activate_on trigger (to keep lazy loading and automatically activate when the event arrives), or declare that it needs to be loaded immediately (lazy_load=False), otherwise it may affect the normal operation of your module.
  3. We do not recommend disabling lazy loading unless there is a special requirement, otherwise it may cause issues such as dependency management and lifecycle events.
  4. In the activate_on command dict declaration, name must be consistent with the actual command name registered in the module's on_load with @command()—otherwise, after the module is activated, the placeholder command will be unregistered, and a command with inconsistent declaration and implementation will not exist.