The PLUGIN_REGISTRY: Central Plugin Registration

Concept: What Is the Registry?

The app can control multiple processes (core app, GUI, server, plugins). They must be centrally registered and configurable. There are two registries for this:

  1. BUILDIN_REGISTRY — core modules firmly defined in start.py
  2. PLUGIN_REGISTRY — plugins dynamically loaded from PLUGIN_REGISTRY.json

The AppConfig Class

Each registry entry is an AppConfig instance (defined in core/models.py):

@dataclass(slots=True)
class AppConfig:
    name: str      # Unique name (e.g. "Timer")
    path: Path     # Absolute path to the EXE
    enable: bool   # Should the plugin start?
    level: int     # Log level for visibility
    ics: bool      # Has GUI? (Interface Control System)

The Five Parameters

ParameterTypeExampleFunction
namestr"Timer"Unique identity (logs, status)
pathPathPath("plugins/timer/main.exe")Absolute path to EXE
enableboolTrueStart plugin at boot?
levelint4Log level for terminal visibility
icsboolTrueSupports GUI window (pywebview)?

[!IMPORTANT] All five parameters are mandatory. If one is missing or an unknown key is present, a ValueError is thrown.

Log Level Meaning

The level parameter controls the terminal visibility depending on log_level in the config.yaml:

LevelNameDescription
0OffHides everything, including GUI windows
1SilentHides console windows, GUI remains active
2DefaultShows only main programs
3AdvancedAlso shows background services
4DebugShows all activated processes
5OverrideShows every process, even if enable=False

Logic: A plugin is visible if log_level >= plugin.level.

Level 0 and Level 5 are special cases:

  • Level 0 hides everything and sets gui_hidden=True
  • Level 5 overrides all enable values and shows everything

BUILDIN_REGISTRY (Core Modules)

The core modules are defined directly in start.py:

BUILDIN_REGISTRY: list[AppConfig] = [
    AppConfig(name="App",              path=APP_EXE_PATH,    enable=True,        level=2, ics=False),
    AppConfig(name="Minecraft Server", path=SERVER_EXE_PATH, enable=True,        level=2, ics=False),
    AppConfig(name="GUI",              path=GUI_EXE_PATH,    enable=GUI_ENABLED, level=2, ics=False),
]

These cannot be changed from outside — they are an integral part of the system.


PLUGIN_REGISTRY (Dynamic Plugins)

Plugins are stored in PLUGIN_REGISTRY.json. This file is automatically loaded at startup:

[
  {
    "name": "Timer",
    "path": "C:\\...\\plugins\\timer\\main.exe",
    "enable": true,
    "level": 4,
    "ics": true
  },
  {
    "name": "Death Counter",
    "path": "C:\\...\\plugins\\deathcounter\\main.exe",
    "enable": true,
    "level": 4,
    "ics": true
  }
]

How Plugins Register

Plugins register via the --register-only flag. The process:

1. registry.exe finds all main.exe in plugins/
   ↓
2. For each main.exe: starts with --register-only
   ↓
3. Plugin outputs AppConfig as JSON (REGISTER_PLUGIN: {...})
   ↓
4. registry.exe writes to PLUGIN_REGISTRY.json
   ↓
5. start.py reads PLUGIN_REGISTRY.json and starts plugins

In the plugin (main.py):

from core import parse_args, register_plugin, AppConfig, get_base_file
from core.utils import load_config

args = parse_args()

if args.register_only:
    register_plugin(AppConfig(
        name="Timer",
        path=get_base_file(),
        enable=cfg.get("Timer", {}).get("Enable", True),
        level=4,
        ics=True
    ))
    sys.exit(0)

# ... rest of the plugin code

[!IMPORTANT] Time limit: The registration process has a hard limit of 5 seconds. Before register_plugin() there must be no slow code (no network access, no I/O operations). After register_plugin(), sys.exit(0) must follow immediately.


How start.py Processes the Registry

start.py goes through both registries and starts the plugins:

for registry in (BUILDIN_REGISTRY, PLUGIN_REGISTRY):
    for app in registry:
        if LOG_LEVEL == 0:
            # Level 0: Hide everything
            start_exe(path=app.path, name=app.name, hidden=True, gui_hidden=True)
        elif LOG_LEVEL == 5:
            # Level 5: Show everything
            start_exe(path=app.path, name=app.name, hidden=False)
        else:
            if app.ics and CONTROL_METHOD == "DCS" and app.enable:
                # GUI plugin in DCS mode: hide GUI, server only
                start_exe(path=app.path, name=app.name,
                          hidden=get_visibility(app.level), gui_hidden=True)
            elif app.enable:
                # Normal start
                start_exe(path=app.path, name=app.name,
                          hidden=get_visibility(app.level))

Scan Cache (Performance)

To speed up the registration process, registry.py uses a scan cache (plugin_registry_scan_cache.json). If a plugin EXE has not changed (same file size and modification time), the result from the cache is used instead of restarting the plugin.


Summary

ComponentFileContent
AppConfigcore/models.pyDataclass with 5 mandatory fields
BUILDIN_REGISTRYstart.pyFirmly defined core modules
PLUGIN_REGISTRYPLUGIN_REGISTRY.jsonDynamically registered plugins
Registrationregistry.pyScans plugins with --register-only
Scan cacheplugin_registry_scan_cache.jsonSpeeds up repeated scans

→ Continue to GUI Architecture