Skip to content

Modules

MasterLaplace edited this page Jul 14, 2026 · 8 revisions

Modules

The LplPlugin engine is organized into 20 independent static libraries (lpl-<name>), each with its own xmake.lua, include/lpl/<name>/ headers, and src/ implementations. The engine/ module serves as the top-level facade that aggregates all others; a separate samples/ tree holds example code.

Build target naming: Each module compiles to lpl-<name> (e.g., lpl-core, lpl-math, lpl-ecs). Code style: SOLID principles, Doxygen /**@ documentation, .inl inline files, guard clauses, no inline comments.


1. core/ — Platform Abstraction

Target: lpl-coreNo dependencies

Platform types, assertions, compile-time utilities, and logging foundations. This is the leaf dependency of the entire module graph.


2. math/ — Mathematical Types

Target: lpl-mathlpl-core

Vec3, Quat, BoundaryBox, Fixed32, CORDIC trigonometry — all without external dependencies (no GLM).

Features:

  • All operators annotated __host__ __device__ (LPL_HD) for CUDA compatibility
  • Deterministic fixed-point arithmetic (Fixed32) for cross-platform physics validation
  • Custom CORDIC implementations for sin/cos/atan2

API:

  • Vec3: +, -, *, /, +=, dot(), cross()
  • Quat: identity(), * (Hamilton), rotate(Vec3)
  • BoundaryBox: contains(Vec3) — inclusion test for migration

3. memory/ — Allocators

Target: lpl-memorylpl-core

PinnedAllocator: STL allocator using cudaHostAlloc (mapped + portable) so that std::vector buffers are directly GPU-accessible via PCIe zero-copy.

Fallback: Without nvcc, uses standard malloc/free.

Usage: All SoA vectors in Partition use PinnedAllocator<T>:

std::vector<Vec3, PinnedAllocator<Vec3>> _positions[2];

This lets the physics kernel obtain a device pointer via cudaHostGetDevicePointer() without any explicit host↔device copy.


4. container/ — Data Structures

Target: lpl-containerlpl-core, lpl-math

FlatAtomicsHashMap

Lock-free hash map specialized for world chunk storage.

  • Contiguous pool: all Partition objects live in a contiguous array (cache-friendly)
  • 64-bit atomic packed entries: [Morton Key (42 bits) | Pool Index (22 bits)]
  • Max capacity: 2²² = 4M items (bounded by the 22-bit index)
  • Open addressing: linear probing with tombstones
  • Slot recycling: free-index stack protected by a SpinLock
Operation Guarantee Mechanism
get() Wait-Free Atomic read
insert() Lock-Free Atomic CAS + SpinLock for pool
remove() Lock-Free Atomic TOMBSTONE + slot recycling
forEach() Thread-Safe Copy _activeSlots under lock

Morton Encoding

Encode 2D/3D coordinates into scalar keys preserving spatial locality (Z-order curve).

  • encode2D(x, y) → 32-bit key (world chunks), bit-interleave via part1by1
  • encode3D(x, y, z) → 63-bit key (octree), bit-interleave via part1by2
  • Bias for negative coordinates keeps everything positive before interleaving:
const uint64_t bias = 1ULL << 20;               // 1,048,576
uint64_t ux = static_cast<uint64_t>(x) + bias;  // -5 → 1,048,571
uint64_t uz = static_cast<uint64_t>(z) + bias;
return Morton::encode2D(ux, uz);                // everything positive

5. concurrency/ — Threading Primitives

Target: lpl-concurrencylpl-core

SpinLock

std::atomic_flag + _mm_pause() — RAII LocalGuard, ~100ns latency under short contention. One per chunk.

ThreadPool

Fixed workers (hardware_concurrency()), supports enqueue (returns std::future) and enqueueDetached (fire-and-forget). Used by SystemScheduler and WorldPartition.


6. ecs/ — Entity Component System

Target: lpl-ecslpl-core, lpl-math, lpl-memory, lpl-container, lpl-concurrency

EntityRegistry

Central registry — sparse set with generational IDs.

  • smartId = (generation << 14) | slot (18-bit gen + 14-bit slot)
  • O(1) lookup: publicId → slot → chunkKey
  • Capacity: 10,000 simultaneous entities, 1,000,000 max public IDs

Partition (Chunk ECS)

Per-chunk SoA container with double buffering, physics, and migration.

SoA Structure:

  • Hot data [2] (PinnedAllocator): positions, velocities, forces
  • Cold data: ids, rotations, masses, sizes, health, neuralControls
  • Metadata: adaptive sparse set _idToLocal, BoundaryBox, SpinLock, FlatDynamicOctree
Method Description
addEntity(snapshot) Thread-safe insertion into BOTH buffers
removeEntityById(id, writeIdx) O(1) swap-and-pop on BOTH buffers
physicsTick(dt, out, wIdx) Gravity + semi-implicit Euler + migration (CPU)
syncBuffers(writeIdx) Copy hot data write→read (memcpy)
setNeuralControl(id, ...) Update BCI data for an entity

Physics (CPU fallback) runs in two passes to avoid double-ticking: a forward pass integrates gravity → acceleration → velocity → position (floor clamp y ≥ 0), then a backward pass does the bounds check and swap-and-pop of out-of-bounds entities (iterating backward so indices stay valid).

Idea — dirty-list tracking: track which entities the network actually modified (atomic bool per entity, or a LIFO of dirty indices) so the GPU recomputes only what changed instead of the whole chunk. Not exploited finely yet.

Dynamic Octree

Full rebuild each frame (no incremental insertion). Internal radix sort (4 passes × 16-bit) on 3D Morton keys, recursive subdivision into 8 octants. Parameters: maxDepth = 8, leafCapacity = 32. Adaptive: brute-force N² for ≤32 entities, octree beyond.

Note: the octree lives in each Partition, but updateSpatialIndex() is not yet called automatically in step() — spatial queries must trigger a rebuild explicitly for now.


7. physics/ — Physics & World Management

Target: lpl-physicslpl-ecs, lpl-math, lpl-container

WorldPartition

Main orchestrator — manages chunks, migration, entity registry, and global double buffering.

step(deltatime) Flow:

forEach chunk → physicsTick (CPU, 2 passes forward+backward)
Final phase: Re-insert migrating entities (getOrCreateChunk + addEntity + updateChunkKey)
GC: Remove empty chunks

Collision (AABB)

  • Minimum penetration axis → collision normal
  • Newton impulse with restitution (e=0.5)
  • Iterative solver (4 complete passes)
  • Auto wake on collision

8. net/ — Networking

Target: lpl-netlpl-core, lpl-math, lpl-memory, lpl-container, lpl-concurrency, lpl-ecs

Network (Transport)

Three modes (résolution automatique) :

  • Kernel driver (production): open("/dev/lpl0")mmap(LplSharedMemory) → zero-copy RX/TX
  • Socket fallback (serveur dev): socket UDP standard
  • Socket forcé (-DLPL_USE_SOCKET, client/Android): socket UDP uniquement
Method Description
network_init() Open driver (or create socket)
network_consume_packets(queue) Drain RX ring/socket → PacketQueue
send_connect/welcome/inputs/state() Typed send methods

PacketQueue

Thread-safe typed queues: ConnectEvent, WelcomeEvent, StateUpdateEvent, InputEvent.

Design note — decoupling: Network no longer knows about WorldPartition or InputManager. It only pushes typed events into the PacketQueue; the ECS systems (Systems.hpp) consume them via drain() and mutate the world. On the server, the constructor tries /dev/lpl0 first and transparently falls back to a UDP socket on port 7777 (_useKernelDriver records the active mode).

SessionManager

Handles client connections server-side. handleConnections() (drain ConnectEvent, skip duplicate IP+port, create player entity from _nextEntityId starting at 100, send MSG_WELCOME, register the endpoint) + broadcast_state() (serialize every entity, 32 bytes each, send MSG_STATE to each client).

Planned: move SessionManager to Read-Copy-Update (RCU) semantics so broadcasts read a stable session snapshot without locking out connections.

Systems

Factory functions (Systems:: namespace) returning SystemDescriptor for the scheduler:

  • NetworkReceiveSystem, SessionSystem, InputProcessingSystem, MovementSystem, PhysicsSystem, WelcomeSystem, StateReconciliationSystem, BroadcastSystem

Client-only systems (BCISystem, LocalInput, Spawn, InputSend, Camera, Render) live in the application, not here, because they pull in external libs (GLFW, Vulkan, BCI backends).

Planned: distribution & netcode

  • ServerMesh (net/ServerMesh) — multi-server worlds where a chunk is the migratable unit; remaining work is entity serialization to a target node and a mesh heartbeat/registration protocol.
  • Rollback netcode (net/netcode/RollbackStrategy) — a circular buffer of serialized state snapshots enabling client-side rollback and reconciliation.

9. gpu/ — GPU Compute

Target: lpl-gpulpl-core, lpl-math

CUDA lifecycle and physics kernel implementation. PhysicsKernel.cu handles gravity and semi-implicit Euler integration on SoA buffers, one thread per entity:

// gravity + semi-implicit Euler, one thread per entity
forces[idx] = Vec3{0.0f, -9.81f * masses[idx], 0.0f};
if (masses[idx] > 0.0001f)
    velocities[idx] += forces[idx] * (1.0f / masses[idx]) * dt;
positions[idx] += velocities[idx] * dt;

API: gpu_init() / gpu_cleanup() (CUDA lifecycle + timing events), launch_physics_kernel(...) (256 threads/block, no sync), gpu_sync() (cudaDeviceSynchronize() — a single barrier for all chunks), gpu_timer_start/stop() via cudaEvent. If __CUDACC__ is undefined, every function is an inline void no-op → transparent g++ compilation.

Ported: PhysicsGPU.cu from legacy → PhysicsKernel.cu (SoA float arrays).


10. input/ — Input Management

Target: lpl-inputlpl-core, lpl-math

InputManager

Unified input state per entity (keys, axes, neural). Used on both server (authoritative from network) and client (local from input devices + BCI).

Method Description
setKeyState(entityId, key, pressed) Update key state
setNeural(entityId, alpha, beta, conc, blink) Update neural data
computeMovementVelocity(entityId, ...) WASD → velocity + neural modulation

Neural speed scale: concentration [0..1] → scale [0.70x .. 1.30x]. Blink jump with rising-edge detection.


11. render/ — Renderer (software + Vulkan)

Target: lpl-renderlpl-shaders, lpl-core, lpl-math, lpl-memory (+ imgui, vulkan-hpp, vulkan-loader, stb, tinyobjloader, glm)

A renderer built around an IRenderer interface with two interchangeable backends and a cross-target parity guarantee (the software path and the Vulkan path produce the same image, checked by RenderParity):

  • VulkanRenderer — device/instance/swapchain, graphics pipeline, ImGui (GLFW + Vulkan backend), texture and model loading (stb, tinyobjloader).
  • SoftwareRasterizer — dependency-free CPU rasterizer (its Topology uses only +,-,*,/ and a hardware sqrt), so a frame can be produced with no GPU at all.

Supporting pieces: Camera, Projection, Material, Pbr (physically-based shading), Lighting, Mesh, Instancing, Texture, Shader, AssetCache, a RayTracer, and Foveated rendering (higher resolution at the gaze centre — a VR/FullDive-oriented optimization). A kernel/ subtree keeps a rendering path usable from the freestanding side.

Note: the renderer is optional. xmake f --renderer=n disables Vulkan and GLFW for headless server builds; the engine then links without lpl-render.


12. audio/ — Spatial Audio

Target: lpl-audiolpl-core

Architecture stub for spatial audio integration. Not yet implemented.


13. haptic/ — Haptic Feedback

Target: lpl-hapticlpl-core

Architecture stub for haptic/vestibular feedback (GVS, tFUS). Not yet implemented.


14. bci/ — Brain-Computer Interface

Target: lpl-bcilpl-core, lpl-math, lpl-input, lpl-container (+ eigen, liblsl, brainflow optional)

Full acquisition-to-metric pipeline. Organized into sub-directories:

Sub-directory Content
src/source/ OpenBCI driver, BrainFlow, LSL, CSV sources
src/dsp/ FFT, windowing, PSD extraction
src/metric/ SignalMetrics (Schumacher), StabilityMetric
src/math/ Covariance, Riemannian geometry
src/stream/ LSL outlet
src/openvibe/ OpenViBE box algorithm stubs
tests/ 24 unit tests (SignalMetrics + RiemannianGeometry)

Key APIs:

OpenBCIDriver

  • Serial capture: 33-byte packets, 8 channels, 250 Hz
  • 24-bit ADS1299 parsing with sign extension
  • FFT (256 pts, Hann window) → PSD per channel
  • Concentration ratio (Beta / (Alpha + Beta)) with EMA smoothing
  • Eye blink detection via thresholding

SignalMetrics

  • schumacher()$R(t) = \frac{1}{N_{ch}} \sum_{i=1}^{N_{ch}} \int_{40}^{70} PSD_i(f,t), df$ — muscle tension
  • integrale() — discrete PSD integration
  • sliding_window_rms() — RMS over sliding window
  • compute_baseline() — mean + σ calibration

RiemannianGeometry

  • compute_covariance() — SPD matrix with Bessel correction
  • riemannian_distance()$\delta_R = \sqrt{\sum_i \ln^2(\lambda_i)}$ — cognitive state change
  • mahalanobis_distance()$D_M$ anomaly detection
  • Jacobi eigenvalue decomposition — no external dependency

NeuralMetrics

  • from_state() — normalized struct with muscle_alert threshold

Calibration (bci/calibration/)

Per-subject calibration: a ~30 s rest phase collects a baseline so metrics like Schumacher R(t) are normalized against the subject's own resting state (baseline_R, σ) instead of absolute values.

Idea — neuro-feedback loop: close the loop so the simulation reacts in real time to NeuralMetrics (concentration, muscle alert).

Note: BrainFlow, liblsl and Eigen are now packaged in the official xmake-repo, so the external BrainFlowSource / LslSource / OpenBciSource sources can be enabled — no longer blocked on missing dependencies.


15. serial/ — Serialization & Replay

Target: lpl-seriallpl-core, lpl-math, lpl-ecs, lpl-net, lpl-input

State serialization and deterministic replay (not the USB serial port — that lives in bci/source/serial/ now). Built around an ISerializable interface plus StateSnapshot (a serialized world state at a given tick):

  • ReplayRecorder — serialize frames + snapshots to disk as the simulation runs.
  • ReplayPlayer — seek to the closest tick by binary search over snapshots, then replay forward.

Because the simulation is deterministic (fixed-point maths, fixed tick), a recorded input+snapshot stream reproduces a run bit-for-bit — the basis for debugging, regression tests and the rollback netcode in net/.


16. platform/ — Hardware Abstraction

Target: lpl-platformlpl-core

Backend interfaces that let the engine run against different hosts (userspace Linux today, the freestanding kernel target tomorrow) behind one API: IPlatform, IClockBackend, IDisplayBackend, IGpuMemoryBackend, IInputBackend. Concrete backends live in platform/linux/ and platform/kernel/; swapping host means swapping a backend, not touching the engine.


17. image/ — 2D Imaging

Target: lpl-imagelpl-core

Dependency-free 2D imaging: Surface and Image buffers, Color, a Painter for software drawing, and a Codec for encode/decode. Used where a frame or texture must be produced without a GPU (software renderer output, tooling, headless capture).


18. scene/ — Scene Graph

Target: lpl-scenelpl-core, lpl-math

Lightweight scene description: a Scene container and Transform2D nodes. Sits above the ECS/renderer to organize what gets drawn.


19. bench/ — Benchmark Harness

Target: lpl-benchlpl-core

The measurement library behind lpl-benchmark: a Harness that times a callback, aggregates runs (mean, min, p99, variance, sample count) and classifies real-time budgets, plus SystemInfo for host/CPU details. Shared so any module can be micro-benchmarked the same way.


20. engine/ — Engine Facade

Target: lpl-engine → ALL other modules (lpl-render only when --renderer is on) + glfw

Top-level facade that depends on every other module. Provides the Core class — the single entry point for the engine.

Core owns by composition:

  • WorldPartition, Network, SystemScheduler, InputManager, PacketQueue, SessionManager

Game loops:

  • run(threaded) — fixed 60Hz server loop
  • runVariableDt(computeDt, onPostLoop) — variable client loop

Lifecycle: Constructor → registerSystem()buildSchedule()run()stop() → Destructor

SystemScheduler

ECS scheduler with automatic component dependency analysis (DAG) and two execution phases:

  • PreSwap: mutation of write buffer (network, inputs, physics)
  • PostSwap: reading of stable read buffer (broadcast, render, camera)

Systems declaring the same write component are serialized; reads are parallelized. Execution proceeds by ordered stages within each phase, with a ThreadPool running same-stage systems concurrently and a std::latch as the stage barrier.

The intended full client pipeline (app-level, some systems pull in GLFW/Vulkan/BCI) reads: NetworkReceive → Welcome → StateReconciliation → BCI → LocalInput → Spawn → Movement → Physics → InputSend (PreSwap), then Camera → Render (PostSwap). The server pipeline is NetworkReceive → Session → InputProcessing → Movement → Physics (PreSwap) then Broadcast (PostSwap).


kernel/ — Linux Kernel Module

Not an xmake target — compiled separately via kernel build system.

lpl_kmod.c

Bidirectional zero-copy network gateway:

RX Path: Netfilter hook NF_INET_PRE_ROUTING intercepts UDP on port 7777 before routing → skb_copy_bits() extracts the payload into an RxPacket (with src_ip/src_port) → the packet is NF_DROPped (never reaches the standard stack). Allocation in hook context uses GFP_ATOMIC.

TX Path: kernel thread lpl_tx_worker polls the TX ring, sleeps on a wait queue (tx_wq) until woken by ioctl(LPL_IOC_KICK_TX), then sends each TxPacket via kernel_sendmsg() on a kernel UDP socket — zero syscall overhead from userspace beyond the ioctl wake.

Shared Memory (LplSharedMemory):

  • mmap() on char device /dev/lpl0
  • vmalloc_user() allocation (contiguous, user-mappable virtual pages)
  • RxRingBuffer rx (4096 slots) + TxRingBuffer tx (4096 slots)
  • Atomic head/tail + cache-line padding; userspace crosses the boundary with smp_load_acquire / smp_store_release (via __atomic builtins)

lpl_protocol

Shared structures between kernel (pure C) and userspace (C++23). The header must stay pure-C compatible for the kernel:

Structure Description
RingHeader head + tail (uint32_t) + 6× padding for cache-line alignment
RxPacket src_ip (4B) + src_port (2B) + length (2B) + data[256] — inbound
TxPacket dst_ip (4B) + dst_port (2B) + length (2B) + data[256] — outbound
LplSharedMemory RxRingBuffer rx + TxRingBuffer tx (single mmap region)
ComponentID TRANSFORM(1), HEALTH(2), VELOCITY(3), MASS(4), SIZE(5), NEURAL(6)

ioctl: LPL_IOC_KICK_TX — wakes the kernel TX thread to drain the ring.

Messages: MSG_CONNECT (0x10), MSG_WELCOME (0x11), MSG_INPUT (0x12), MSG_STATE (0x13)


Tech Stack & Build | Next: Architectural Decisions

Clone this wiki locally