Nodens

← All projects

IFRAMEWORK experimental

Nodens

A C++23 framework for building interactive desktop applications quickly, with optional Dear ImGui. Everything is a C++ module, so an app starts with import nodens; and links one static library.

  • 01 · Windowing

    GLFW windows

    Creates the window, graphics context and input handling, with OpenGL or Vulkan backends.

  • 02 · Lifetime

    Layer system

    An app is a stack of layers. They update in order, draw back to front, and receive input from the top down.

  • 03 · Messaging

    Event system

    Input events pass down the layers. A thread-safe event bus lets any part publish and subscribe to custom events, handled immediately or queued.

  • 04 · Concurrency

    Job system

    Runs heavy work, like simulation and data processing, on worker threads so the render thread stays responsive.

Writing an app

  • An app inherits from Nodens::Application, pushes one or more layers, and calls Run(). Every frame, each layer receives OnEvent, OnUpdate(TimeStep) and OnImGuiRender(TimeStep).
  • Two examples ship with it: circularwave3d (live 2D/3D plots with ImPlot and ImPlot3D) and planetaryscan (the event bus and job system working together).
  • Tracy profiling is built in: add ZoneScoped to any layer method.
  • CMake builds every dependency from source, so there is nothing to install by hand.
a minimal app

  • 01 · Modules

    Module-first

    Public APIs are C++ module interfaces (.cppm). Third-party headers stay in the global module fragment, so an app only ever writes import nodens;.

  • 02 · Delivery

    One static library

    The framework builds into a single static library, so the final product is one portable executable.

  • 03 · Platform

    Linux, on purpose

    The validated environment is Arch Linux, shared with Nyar through one dev container image. Native Windows and macOS builds are not supported.

Layer lifecycle

Every layer is a subclass of ILayer and overrides only the hooks it needs.

HookWhen it runsUse it for
OnAttachOnce, when the layer is pushed onto the stackAllocating resources, subscribing to events
OnUpdate(ts)Every frame, with the time since the last frameSimulation and application logic
OnImGuiRender(ts)Every frame, between the start and end of the ImGui frameWindows, plots and debug tools
OnInputEvent(event)When an input or window event reaches the layer, back to frontHandling input; set Handled to stop it travelling further
OnDetachOnce, when the layer is removedReleasing resources, unsubscribing

Layers and overlays

The layer stack is one ordered list with a boundary in it. PushLayer inserts before the boundary and PushOverlay appends after it, so overlays such as the ImGui layer always update and draw after regular layers. The stack owns the layer pointers and deletes them on shutdown.

Two event paths

Events that come from the window and events your own code defines take different routes.

  • Input and window events

    Layer stack

    They travel as one std::variant, back to front through the layers, until one handles it.

  • Events you define

    EventBus

    Subscribers register typed handlers. Dispatch calls them at once, in subscription order, on the calling thread. QueueEvent copies the event into a double-buffered byte array that Flush() drains once per frame on the main thread.

Event types

  • 01 · Plain data

    Events are POD

    An event is a standard-layout, trivially copyable struct with a static constexpr std::string_view Name. A C++ concept enforces it, so events can be copied straight into byte buffers.

  • 02 · No RTTI

    Sequential type IDs

    Each event type gets the next integer from an atomic counter the first time it is used. No RTTI and no hashing.

  • 03 · Threads

    Safe where it matters

    Subscribe, Unsubscribe and QueueEvent may be called from any thread. Dispatch and Flush belong to the main thread.

Events and jobs together

  • The job system runs work on a pool of std::jthread workers, sized to the hardware thread count minus one so the main thread stays free.
  • Submit takes any callable and returns a std::future. Workers sleep on a condition variable that understands stop tokens, so shutdown cancels cooperatively and joins on its own.
  • A job finishes by queueing an event. The next Flush() hands the result to subscribers on the main thread, so no handler needs a lock of its own.
  • The planetaryscan example is this pattern with live plots.
events and jobs

Profiling with Tracy

  • 01 · CPU

    Zones and frames

    Add ZoneScoped to any function to see it in Tracy. The job queue depth is plotted on its own timeline.

  • 02 · Threads

    Lock contention

    The job queue mutex is a Tracy lockable, so contention between workers is visible as it happens.

  • 03 · GPU

    Vulkan timings

    The Vulkan backend keeps per-frame Tracy contexts for GPU profiling next to the CPU view.

Where it comes from

Nodens began as a personal project for my undergraduate Computer Graphics course at UFMG. Its foundations follow the early episodes of The Cherno's Game Engine series, and earlier iterations powered several of my academic projects, including boids and galaxians. Versions of it also ran the numerical simulation applications from my research, which are on my portfolio.

Examples

Both ship in the repository under examples/.

ExampleShows
circularwave3dThe immediate-mode GUI, with live 2D and 3D plots from ImPlot and ImPlot3D
planetaryscanThe event bus and job system together, with thread-safe data collection and a live scatter plot

Get started

References