-
Notifications
You must be signed in to change notification settings - Fork 10
Architecture
This page is the mental model. Before you write an addon, it helps to see what the engine is made of, where your code attaches, and how a teleport actually flows through it. Every diagram below is a real boundary in the codebase, not a marketing picture.
!!! note "Why this matters for addon authors"
LeafRTP is structurally different from a classic random-teleport plugin. Legacy plugins reroll random coordinates synchronously on the main thread until one happens to be safe - an unbounded loop that stalls the server. LeafRTP instead pre-generates and validates locations off-tick, selects them with a bounded O(log n) lookup over an Archimedean-spiral mapping, and remembers bad sectors across restarts. You extend that machine by composing its seams - you never re-implement the safety it guarantees.
LeafRTP is a multi-module build. The two modules you compile against (rtp-api, and occasionally rtp-core) are platform-neutral; everything Bukkit/Paper/Folia/Fabric-specific lives in a platform adapter that you never touch.
graph TD
subgraph contract["Contract tier (depend on this)"]
api[rtp-api<br/>stable interfaces + models]
cmd[commands-api]
fx[effects-api]
maps[maps-api]
metrics[metrics-api]
end
subgraph impl["Implementation tier (derive only when needed)"]
core[rtp-core<br/>regions, queues, spiral math,<br/>MemoryTracker, pipeline]
end
subgraph platform["Platform adapters (never import from these)"]
bukkit[rtp-bukkit]
paper[rtp-paper]
folia[rtp-folia]
fabric[rtp-fabric]
neoforge[rtp-neoforge]
end
entry[rtp-plugin / mod entry point]
yours[Your addon]
api --> core
cmd --> core
fx --> core
maps --> core
metrics --> core
core --> entry
bukkit --> entry
paper --> entry
folia --> entry
fabric -.-> core
neoforge -.-> core
yours -->|compile against| api
yours -.->|reach in when you<br/>need to| core
!!! tip "Pick the lowest tier that does the job - but nothing is off-limits"
Most addons never need more than the contract tier (rtp-api + the sibling *-api modules): menus, triggers, metrics exporters, and effects all live there, and adding a new destination geometry (RTP.addShape) or vertical placement (RTP.addVerticalAdjustor) is the common reason to pull in rtp-core. But the two-tier split (ADR-051) is a stability promise, not a fence: rtp-api is the surface we keep stable for you, while rtp-core is everything else - regions, queues, the pipeline, MemoryTracker. If you are smart enough to drive a core internal, it is fair game; you simply trade the stability guarantee for the extra reach, and you still owe the engine its safety rules (S-001..S-007, schedule through RTP.scheduler). Prefer the lowest tier that does the job, then go deeper when you have to.
!!! warning "Going deep is a trade, not a free lunch"
rtp-core symbols can change between releases without notice (that is precisely what the contract tier shields you from). When you bind to a core internal, pin the version you built against and re-test on upgrades. See API stability.
!!! warning "The dependency rule is enforced"
rtp-core and rtp-api must never import platform classes (org.bukkit.*, Fabric, etc.). ArchUnit tests fail the build if they do. Your addon should follow the same discipline: depend on rtp-api, and route anything platform-specific through the accessors the engine hands you (RTP.scheduler, RTP.serverAccessor).
For the full module breakdown in prose, see Addon development and the canonical docs/dev/ARCHITECTURE.md.
Each extension surface attaches at a different point of the engine. This is the same set of seams catalogued on Extending RTP, drawn as attachment points rather than a table.
graph LR
player([Player / trigger])
subgraph engine["LeafRTP engine"]
sel[Location selector<br/>spiral + shapes + vert]
queue[Pre-generation queue]
pipe[Teleport pipeline<br/>validates + teleports]
cfg[ConfigParser]
sched[RTP.scheduler]
net[NetworkTransport SPI]
end
menu[[Menu / GUI addon]]
subcmd[[Sub-command addon]]
verifier[[Verifier hook]]
shape[[Custom Shape / VertAdjustor]]
effect[[effects-api effect]]
exporter[[Metrics exporter]]
xserver[[Cross-server menu]]
player --> menu --> pipe
player --> subcmd --> pipe
verifier --> sel
shape --> sel
effect -. warmup window .-> pipe
exporter -. reads .-> queue
xserver --> net
menu -. reads status .-> queue
| Attachment point | What you build | Tier | Detail page |
|---|---|---|---|
In front of teleport(...)
|
menu / GUI, NPC / sign / item trigger | contract | Building a menu, Common RTP recipes |
| Inside the selector | verifier (claim skip, "avoid water"), new Shape, new VerticalAdjustor
|
contract / impl | Hooks and verifiers, Shapes, Vertical adjustors |
| The command tree | /rtp <verb> |
contract | Sub-commands |
| The warmup window | particle / bossbar / "rift" effect | contract | Writing an effect |
| Reads off the queue/metrics | dashboards, Prometheus/InfluxDB exporter | contract | Metrics |
NetworkTransport |
proxy / cross-server destination rows | proxy SPI | Cross-server RTP for addons |
When a player asks for a destination, almost all the cost has already been paid by the background queue. The fast path is a dequeue, not a search.
flowchart TD
start([Player runs /rtp or addon calls teleport]) --> perm{Permission OK?}
perm -- no --> deny[Send no-permission message, stop]
perm -- yes --> econ{Economy OK?}
econ -- no --> funds[Send insufficient-funds message, stop]
econ -- yes --> dq[Dequeue a pre-generated location]
dq --> ready{Queue has a ready location?}
ready -- "yes (normal, instant)" --> tp[Teleport player]
ready -- "no (cold start / scan not run)" --> gen[Generate on demand<br/>async chunk load, 1-2 ticks] --> tp
tp --> invuln[Apply invulnerability timer] --> refill[Background task refills queue async]
!!! warning "Never a silent no-op (REQ-RTP-S-004)"
Every branch ends in either a teleport or an explicit message. When you call RTPAPI.teleport(...) from an addon you get a typed RTPResult back (success / queued / a reason) - it is never a silent failure. Surface that result to the player; do not swallow it.
The validation each candidate passes through before it ever reaches the queue is shape -> chunk -> vert -> biome -> safety (the TeleportPipelineTask). That is the work an addon never has to redo.
Pre-generated locations sit in a layered cache. Reads fall through from hottest to coldest; the background task and /rtp scan push verified locations upward.
flowchart LR
poll([poll for a location]) --> L1
L1["L1 - kept cache<br/>chunks loaded, ready to serve"] -- empty --> L2
L2["L2 - cold cache<br/>verified, chunks released"] -- empty --> L3
L3["L3 - backlog cache<br/>unverified FIFO, optional"] -- empty --> ondemand["generate on demand"]
scan[/"rtp scan"/] --> L3
bg[background QueueTask] --> L1 & L2
| Tier | Code symbol | State |
|---|---|---|
| L1 (hot / "kept") | keptLocations |
Verified, chunks force-loaded with keep(true) - what /rtp normally serves |
| L2 (cold / "unkept") | unkeptLocations |
Verified, chunks released; re-loaded on use |
| L3 (backlog) | backlogLocations |
Optional unverified FIFO upstream of L2; promoted as the anvil pre-filter verifies bins |
!!! note "This is the bring-up sequence"
A freshly started server has empty caches, so the first teleport may pause to generate. The recommended setup order is install -> configure region geometry -> /rtp scan start region=<name> for each reasonably-sized region, which warms the caches before players arrive. See Scan and spatial memory.
Bad sectors are remembered across restarts in a small spatial-memory database, so the queue never starts truly cold after the first run.
The single rule that keeps an addon safe on every platform - especially Folia, where touching the wrong region's data throws ThreadAccessException - is to route all periodic/async work through RTP.scheduler. The adapter is the only layer that knows what kind of thread the work lands on.
flowchart TD
addon[Your addon] --> sched[RTP.scheduler<br/>RTPScheduler SPI]
sched --> bukkit[Bukkit/Paper:<br/>Bukkit scheduler + async pool]
sched --> folia[Folia:<br/>region / entity / global schedulers]
sched --> fabric[Fabric/NeoForge:<br/>server-thread executor]
!!! warning "No raw Executors or new Thread(...) in backend addons"
A hand-rolled thread bypasses Folia region ownership, MemoryTracker accounting, and the shutdown drain, and leaks across /reload. Convert a period in milliseconds to ticks (Math.max(1L, ms / 50L)) and use RTP.scheduler.runTaskTimerAsynchronously(...). See Addon development.
The diagrams above are the addon-author's mental model. When you need the exact per-subsystem flow - every state, branch, and cleanup edge - the engine ships a set of normative Mermaid charts under docs/architecture/, each paired with a walkthrough in docs/dev/CODE_TOUR.md.
| Diagram | Covers |
|---|---|
| 01 - Teleport execution pipeline | End-to-end lifecycle of one /rtp: cache-vs-queue-vs-unqueued branch, async SETUP/LOAD, region-thread safety eval, entity-scheduler teleport, guaranteed cleanup |
| 02 - Budgeted cache generator | The background queue-refill loop that keeps each region's cache warm |
| 03 - Chunk ticket lifecycle | How chunk tickets are issued and released (S-002) |
| 04 - Active GC sweep | The MemoryTracker periodic reaper |
| 05 - Scan task crawler | The /rtp scan region safety pre-scanner |
| 06 - Plugin setup lifecycle | Startup / init ordering (when your onLoad() runs) |
07 - /rtp command region selection |
How a region/world is resolved before the pipeline runs |
| 08 - Location selection per attempt | Accept/reject of one candidate (x, y, z) - where verifiers and shapes act |
| 09 - Configuration load and reload |
ConfigParser load / /rtp reload
|
| 10 - Shutdown and flush lifecycle | Drain / flush on disable |
| 11 - Configuration write and persist | How config writes are persisted |
| 12 - Network model | Multi-server / multi-proxy topology, cross-server /rtp lifecycle, reservation-token state machine |
!!! tip "Map the addon seams to these charts"
A verifier or custom Shape acts inside diagram 08; a menu/trigger sits in front of diagram 01; an effect runs on the warmup window of diagram 01; a metrics exporter reads the cache filled by diagram 02; a cross-server menu rides diagram 12.
- Addon development - the happy-path landing page (dependency setup + first addon).
- Extending RTP - every extension surface in prose.
- Behavior and Scan and spatial memory - the runtime story for admins.
- docs/dev/ARCHITECTURE.md and docs/dev/CONCEPTS.md - the canonical engineering sources.