Skip to content

Architecture ​

This document describes Ageratum's module layout, core data flows, and extension mechanisms, to help developers understand the codebase for maintenance and contribution.


High-Level Architecture ​

┌─────────────────────────────────────────────────────┐
│                    Game Client                      │
│                                                     │
│  /ageratum command  ──→  AgeratumClient             │
│  Network packet (server) ──→  ClientPayloadHandler  │
│                          │                          │
│                          ▼                          │
│                    GuideScreen (GUI)                │
│                          │                          │
│              ┌───────────┴──────────────┐           │
│              ▼                          ▼           │
│    GuideDocumentCache            GuideDocumentLoader│
│    (pre-parsed cache)            (path utilities)   │
│              │                                      │
│              ▼                                      │
│       MarkdownParser                                │
│    (block-level + inline)                           │
│              │                                      │
│              ▼                                      │
│       MDComponent List                              │
│    ┌──────────────────────────┐                     │
│    │ MDTextComponent          │                     │
│    │ MDHeaderComponent        │                     │
│    │ MDCodeBlockComponent     │                     │
│    │ MDListComponent          │                     │
│    │ MDTableComponent         │                     │
│    │ MDQuoteComponent         │                     │
│    │ MDImageComponent         │                     │
│    │ MDNoticeBoxComponent     │                     │
│    │ MDRecipeComponent        │                     │
│    │ MDItemComponent          │                     │
│    │ MDBlockComponent         │                     │
│    │ MDEntityComponent        │                     │
│    │ MDLatexComponent         │                     │
│    │ MDRowComponent           │                     │
│    │ MDNBTStructureComponent  │                     │
│    │ <custom components>      │                     │
│    └──────────────────────────┘                     │
└─────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────┐
│                  Network Layer (Bidirectional)       │
│                                                      │
│  OpenGuidePayload    (Server -> Client)             │
│  ShareGuidePayload   (Client -> Server)             │
│                                                      │
│  ClientPayloadHandler / ServerPayloadHandler         │
└──────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────┐
│              NeoForge Custom Registries              │
│                                                      │
│  ageratum:extension_component_factory                │
│    → MDExtensionComponentFactory (block extensions)  │
│                                                      │
│  ageratum:inline_component_factory                   │
│    → MDInlineComponentFactory (inline components)    │
│                                                      │
│  ageratum:inline_style_parser                        │
│    → MDInlineStyleParser (inline styles)             │
│                                                      │
│  ageratum:recipe_component_factory                   │
│    → MDRecipeComponent.RecipeComponentFactory        │
└──────────────────────────────────────────────────────┘

Module Descriptions ​

Main Class (Ageratum) ​

  • Declares MOD_ID
  • Provides location(path) utility
  • Provides openGuide(player, location) server-side entry point

Client Initialization (AgeratumClient) ​

  • Registers NeoForge custom registries (AgeratumRegistries.register())
  • Triggers class loading of built-in registrations (prevents JIT lazy-init from missing entries)
  • Registers the /ageratum client command
  • Registers the document pre-load reload listener (RegisterClientReloadListenersEvent)
  • Provides preview-mode entry (/ageratum preview) and file polling refresh

Networking and Collaboration (AgeratumNetwork) ​

  • OpenGuidePayload: server -> client, asks client to open a guide
  • ShareGuidePayload: client -> server, requests broadcasting a share message
  • ServerPayloadHandler: filters by sameTeam and sends clickable /ageratum commands

Parsing Pipeline ​

Raw .md file text
        │
        ▼
  MarkdownParser.parseDocument()
        │
        ├─ Extract Front Matter (--- ... ---)
        │
        ├─ Collect reference link definitions ([ref]: url)
        │
        ├─ Block-level parsing (line-by-line scan)
        │   ├─ Code fence detection
        │   ├─ Extension syntax (::: and <tag>)
        │   ├─ Lists, blockquotes, tables
        │   ├─ Headings, horizontal rules
        │   ├─ Images
        │   └─ Paragraphs (default)
        │
        └─ MDDocument { frontMatter, components }
                │
                ▼
         Component list passed to GuideScreen for rendering

Inline Parsing (MDComponent.textFormat()) ​

Raw text
    │
    ├─ Pre-process escape characters (\* → %%MDESC42%%)
    │
    ├─ Expand reference links
    │
    ├─ Extract code spans (`code`) to prevent inner content parsing
    │
    ├─ Autolinks (<url>, <email>)
    │
    ├─ Standard links ([text](url))
    │
    ├─ Bold / Italic / Strikethrough
    │
    ├─ Custom inline styles
    │   (query INLINE_STYLE_PARSER_REGISTRY, sorted by priority)
    │
    └─ Restore escape characters
    │
    ▼
  FormattedText

Cache Mechanism (GuideDocumentCache) ​

Resource pack load/reload event
        │
        ▼
  RELOAD_LISTENER.prepare()
        │
        ├─ Scan all ageratum/**/*.md files
        ├─ Parse each into MDDocument
        └─ Build NavigationTree (directory structure)
        │
        ▼
  RELOAD_LISTENER.apply()
        │
        ├─ Atomically replace PARSED_DOCUMENT_CACHE (volatile Map)
        └─ Atomically replace NAVIGATION_TREE_CACHE

The cache uses volatile variables for thread visibility and Map.copyOf() for immutability.


Extension Mechanisms ​

Extension Component Registration Flow ​

External mod registers
DeferredRegister<MDExtensionComponentFactory>
        │  .register(modEventBus)
        ▼
NeoForge registration event
        │
        ▼
AgeratumRegistries.EXTENSION_COMPONENT_FACTORY_REGISTRY
        │  lookup by ResourceLocation (namespace:name)
        ▼
MarkdownParser encounters extension syntax
        │
        ▼
MDExtensionComponentFactory.create(context)
        │
        ▼
Returns MDComponent → added to document component list

Inline Style Resolution Flow ​

MDComponent.textFormat(rawText)
        │
        ▼
Query AgeratumRegistries.INLINE_STYLE_PARSER_REGISTRY
(all registered parsers, sorted by priority ascending)
        │
        ▼
Scan text character by character:
  for each parser (ascending priority):
    parser.parse(text, pos) → InlineStyleMatch or null
        │
        ▼
Take the earliest match (smallest startPos);
if tie on position, take the lowest priority value
        │
        ▼
Apply Style to matched range, recursively process inner text
        │
        ▼
Output FormattedText

Inline Component Resolution Flow ​

MDComponent.textFormat(rawText)
        │
        ▼
Query AgeratumRegistries.INLINE_COMPONENT_FACTORY_REGISTRY
        │
        ▼
Match self-closing inline tag: <namespace:id .../>
        │
        ▼
Build MDInlineComponentContext
        │
        ▼
MDInlineComponentFactory.create(context)
        │
        ▼
Append returned FormattedText to the current inline output

Design Principles ​

1. Separation of Concerns (SoC) ​

Each class has a single responsibility:

  • MarkdownParser: text → component list only
  • GuideDocumentLoader: path normalization and IO only
  • GuideDocumentCache: preload cache management only
  • GuideScreen: GUI rendering and user interaction only

2. Client-Side Isolation (@Mod(dist = Dist.CLIENT)) ​

All rendering-related code (GUI, Markdown parsing, registries) is isolated to the client side via @Mod(dist = Dist.CLIENT). The server side holds only Ageratum (no render code) and AgeratumNetwork.

3. Registry-Driven Extension ​

All extension points are implemented via NeoForge Custom Registries, not reflection or interface scanning. Benefits:

  • Registration lifecycle aligns with the game (frozen after FMLCommonSetupEvent)
  • Supports DeferredHolder lazy references
  • Automatic conflict detection for duplicate keys

4. Preload + Cache ​

All documents are parsed once on resource pack load and cached. This eliminates IO and parsing overhead on every document open. The cache is invalidated and rebuilt automatically on resource pack reload.

5. Language Fallback ​

When looking up documents, Ageratum first tries the client's current language (e.g. zh_cn), then falls back to en_us if not found. This simplifies mod development: providing only the en_us version is sufficient for all language clients.

6. Preview Isolation ​

Preview documents use a dedicated namespace ageratum_review and are read from local previewPath instead of resource-pack caches, avoiding pollution of normal guide indexing.


File Size Guidelines ​

The project follows a "no oversized files" policy:

File TypeLine LimitPolicy
Single Java class~400 linesSplit if exceeded
Inner classesExtracted to separate files
Static utility methodsMoved to dedicated utility class

Testing Strategy ​

The current version has no unit tests. Functionality is verified through in-game testing (/ageratum command + resource packs).

Planned future additions:

  • Unit tests for MarkdownParser text output (game-environment-independent)
  • Path normalization tests for GuideDocumentLoader

See Also ​

Released under the CC-BY-NC-SA 4.0 License.