MayaFlux is a C++20/23 framework for real-time computation across sound, geometry, image, and network. All data is the same numerical substrate; the domain annotation decides where a buffer cycle goes, not what the data is.
This guide covers environment setup and the structure of a MayaFlux program. Tutorials that build on this foundation are at mayaflux.org/tutorials.
Weave: Recommended for Most Users
Weave is the installer and project tool for MayaFlux. Download it from the Weave releases page.
Installing MayaFlux
Launch Weave and choose Install MayaFlux. Weave downloads the framework, installs all required dependencies, and configures your environment. Restart your terminal when it finishes.
Creating a Project
Launch Weave and choose Create Project. Enter a name and pick a destination. Weave generates a ready-to-build project:
MyProject/
├── CMakeLists.txt
├── CMakePresets.json
├── community.cmake
├── cmake/
│ ├── mayaflux.cmake
│ ├── shaders.cmake
│ └── build_community.cmake
├── src/
│ ├── main.cpp
│ └── user_project.hpp
├── data/
│ └── shaders/
└── .gitignore
src/user_project.hpp is where you write your code. CMakeLists.txt is yours to edit; the MayaFlux integration lives in cmake/mayaflux.cmake and is included automatically.
CMakePresets.json ships with every project and defines debug and release presets. CLion, VS Code with the CMake extension, and Visual Studio all pick up the presets automatically when you open the project folder.
cmake --preset release
cmake --build --preset release
./build/MyProject
On Windows the binary lands in build\MyProject.exe.
Live Coding (Lila)
Check Enable Live Coding (Lila) in the project creation dialog to embed the Lila JIT compiler in your process. Connect LilaCode (VS Code) or lila.nvim (Neovim) to evaluate C++ against your running application in real time.
Community Modules
Community modules are C++ source libraries that compile directly into your project with no plugin boundary. In Weave, open your project and choose Add Community Module. Weave fetches the community registry, checks version compatibility, clones the module into community/<name>/, and registers it in community.cmake. Rebuild after adding modules.
After Weave completes setup, jump to Program Structure.
Building from Source
Requirements: All Platforms
- Compiler: C++20 (GCC 12+, Clang 16+, MSVC 2022+)
- Build system: CMake 3.28+
- Required dependencies: Vulkan SDK, FFmpeg (avcodec, avformat, avutil, swresample, swscale, avdevice), GLM, Eigen, HIDAPI, Asio, Assimp, FreeType, utf8proc, nlohmann_json, fmt, TBB, STB, LLVM 21+
All dependencies are required. CMake will not generate if any are missing.
Requirements: Platform-Specific
Linux:
- PipeWire (audio and MIDI)
- libdbus-1 (XDG Portal file dialogs)
- wayland-protocols, libwayland-client, xkbcommon (windowing)
- fontconfig (system font discovery)
macOS:
- macOS 26+ (Tahoe), Apple Silicon or Intel
- Apple Clang 17+ via Xcode Command Line Tools
- GLFW (via Homebrew, for windowing)
- Frameworks linked automatically: CoreAudio, AudioUnit, AudioToolbox, CoreMIDI, AppKit, UniformTypeIdentifiers, CoreFoundation
Windows:
- Visual Studio 2022+ (MSVC) or MinGW-w64
- LLVM 22+ (for Lila JIT)
Build
git clone https://github.com/MayaFlux/MayaFlux.git
cd MayaFlux
# Install all dependencies and configure environment variables
./scripts/setup_macos.sh # macOS
./scripts/setup_linux.sh # Linux
.\scripts\win64\setup_windows.ps1 # Windows - must be run as Administrator (requires UAC elevation)
# Build using CMakePresets
cmake --preset linux-release # or macos-release / windows-release
cmake --build --preset linux-release
setup_windows.ps1 installs all required packages via packages.psd1 and configures environment variables. It must be run elevated; UAC will prompt if you launch it normally.
Program Structure
A MayaFlux program has two entry points defined in src/user_project.hpp:
#pragma once
#define MAYASIMPLE
void settings() {
}
void compose() {
}
Core::GlobalStreamInfo stream
Core::GlobalStreamInfo & get_global_stream_info()
Gets the stream configuration from the default engine.
main.cpp calls Init(), settings(), Start(), compose(), Await(), and End() in order. You do not edit it unless you need custom engine configuration beyond what settings() exposes.
MAYASIMPLE
Defining MAYASIMPLE before including MayaFlux.hpp pulls in the full concrete type set and brings all MayaFlux namespaces into scope. Without it you get the API surface only. User projects built via Weave define it by default.
The <tt>vega</tt> global
vega is the global Creator instance. It is the factory for all computation objects: generators, filters, networks, buffers, containers, mesh loaders, input nodes. Every object created through vega is registered with the engine automatically.
auto sine = vega.
Sine(440.0, 0.5) | Audio[0];
auto audio = vega.
read_audio(
"res/drum.wav") | Audio;
auto img = vega.
read_image(
"res/texture.png") | Graphics;
auto Sine(Args &&... args) -> std::shared_ptr< MayaFlux::Nodes::Generator::Sine >
auto read_audio(const std::string &filepath) -> std::shared_ptr< Kakshya::SoundFileContainer >
auto ModalNetwork(Args &&... args) -> std::shared_ptr< MayaFlux::Nodes::Network::ModalNetwork >
auto read_mesh_network(const std::string &filepath, IO::TextureResolver resolver=nullptr) -> std::shared_ptr< Nodes::Network::MeshNetwork >
auto read_image(const std::string &filepath) -> std::shared_ptr< Buffers::TextureBuffer >
The | Audio and | Graphics operators are domain annotations. They attach a processing token that decides which subsystem drives the object and at what rate. They do not change what the object is.
A First Program
The example below is complete and runnable. It loads a texture, generates a parametric surface, and animates it frame by frame via a coroutine inside a Nexus entity. A movable light agent influences all render processors simultaneously. Keyboard input moves the light.
#pragma once
#define MAYASIMPLE
void settings()
{
}
void compose()
{
auto view = []() -> Kinesis::ViewTransform {
return Kinesis::look_at_perspective(
glm::vec3(0.0F, 2.0F, 5.0F),
glm::vec3(0.0F),
glm::radians(45.0F), 1920.0F / 1080.0F, 0.1F, 100.0F);
};
auto surf_fn = [](float u, float v, float t) -> glm::vec3 {
float a = u * glm::two_pi<float>();
float b = v * glm::two_pi<float>();
return {
std::sin(
a + t) * (1.0F + 0.3F * std::cos(3.0F *
b)),
std::sin(2.0F *
a - t) * std::sin(
b) * 0.5F,
std::cos(
a + t) * (1.0F + 0.3F * std::cos(3.0F *
b)),
};
};
auto data = Kinesis::generate_parametric_surface(
[&](float u, float v) { return surf_fn(u, v, 0.0F); }, 64, 64);
auto surf_node = std::make_shared<MeshWriterNode>(data.vertex_count()) | Graphics;
surf_node->set_mesh(data);
surf_buf->set_texture(img->get_gpu_texture());
surf_buf->setup_rendering({
.target_window = window,
.fragment_shader = "mesh_textured_lit.frag.spv",
});
surf_buf->get_render_processor()->set_view_transform_source(view);
auto& light_pos = make_persistent<glm::vec3>(0.0F, 1.5F, 0.0F);
constexpr glm::vec3 half { 0.04F, 0.04F, 0.04F };
auto light = std::make_shared<Nexus::Agent>(
0.0F,
[](const Nexus::PerceptionContext&) { },
[](const Nexus::InfluenceContext&) { });
light->set_position(light_pos);
light->set_color(glm::vec3(1.0F, 0.9F, 0.7F));
light->set_intensity(1.5F);
light->set_radius(4.0F);
light->render(mgr, { .target_window = window, .topology = Portal::Graphics::PrimitiveTopology::LINE_LIST });
light->get_render_processor(window)->set_view_transform_source(view);
light->set_vertices<
LineVertex>(Kakshya::to_line_vertices(
Kinesis::cuboid_wireframe(light_pos, half, glm::vec3(1.0F, 0.9F, 0.7F))));
light->add_influence_target(surf_buf->get_render_processor());
auto fabric = make_persistent_shared<Nexus::Fabric>(
fabric->wire(light)
.every(1.0 / 60.0, Vruta::ProcessingToken::FRAME_ACCURATE)
.finalise();
auto surf_driver = std::make_shared<Nexus::Emitter>(
[surf_node, surf_fn, &st](const Nexus::InfluenceContext&) {
auto d = Kinesis::generate_parametric_surface(
[&](float u, float v) { return surf_fn(u, v, st); }, 64, 64);
const auto& vb = std::get<std::vector<uint8_t>>(d.vertex_variant);
surf_node->set_mesh_vertices(
std::span(
reinterpret_cast<const MeshVertex*
>(vb.data()), d.vertex_count()));
st += 0.01F;
});
fabric->wire(surf_driver)
.every(1.0 / 60.0, Vruta::ProcessingToken::FRAME_ACCURATE)
.finalise();
static constexpr float step = 0.05F;
auto move = [light, half, &light_pos, &mgr](glm::vec3 delta) {
light_pos += delta;
light->set_position(light_pos);
light->set_vertices<
LineVertex>(Kakshya::to_line_vertices(
Kinesis::cuboid_wireframe(light_pos, half, glm::vec3(1.0F, 0.9F, 0.7F))));
};
auto mk_mover = [&](glm::vec3 d) {
return std::make_shared<Nexus::Emitter>(
[move, d](const Nexus::InfluenceContext&) { move(d); });
};
fabric->wire(mk_mover({ step, 0, 0 })).on(window, IO::Keys::D, true).finalise();
fabric->wire(mk_mover({ -step, 0, 0 })).on(window, IO::Keys::A, true).finalise();
fabric->wire(mk_mover({ 0, step, 0 })).on(window, IO::Keys::W, true).finalise();
fabric->wire(mk_mover({ 0, -step, 0 })).on(window, IO::Keys::S, true).finalise();
fabric->wire(mk_mover({ 0, 0, step })).on(window, IO::Keys::Q, true).finalise();
fabric->wire(mk_mover({ 0, 0, -step })).on(window, IO::Keys::E, true).finalise();
window->show();
}
auto GeometryBuffer(Args &&... args) -> std::shared_ptr< MayaFlux::Buffers::GeometryBuffer >
Kakshya::LineVertex LineVertex
Kakshya::MeshVertex MeshVertex
std::shared_ptr< Vruta::EventManager > get_event_manager()
Gets the event manager from the default engine.
Creator vega
Global Creator instance.
std::shared_ptr< Core::Window > create_window(const Core::WindowCreateInfo &create_info)
Create a new window with specified parameters.
T & make_persistent(Args &&... args)
Construct a T in place, retain it for process lifetime, and return a direct reference.
std::shared_ptr< Buffers::BufferManager > get_buffer_manager()
Gets the buffer manager from the default engine.
std::shared_ptr< Vruta::TaskScheduler > get_scheduler()
Gets the task scheduler from the default engine.
What this shows:
vega.read_image and vega.GeometryBuffer are factory calls; domain annotation follows at the call site
- A
GraphicsRoutine coroutine inside a Nexus::Emitter owns its animation loop, suspended one frame at a time via FrameDelay
- A
Nexus::Agent acting as a light registers influence targets directly on render processors; moving the light position updates all of them simultaneously
- Keyboard input wires through
Wiring::on(key, held) - each key is its own entity, each entity's influence function applies the delta
What to Read Next
FAQ
macOS: do I need Homebrew LLVM?
No. MayaFlux compiles with Apple Clang from Xcode Command Line Tools. Homebrew LLVM is not used for compilation. LLVM is required as a runtime dependency for Lila (the JIT environment) and is installed by the setup script, but it is not your compiler.
What is the minimum macOS version?
macOS 26 (Tahoe) on both Apple Silicon and Intel. Earlier versions lack the C++20 stdlib coverage MayaFlux requires.
What happens if a dependency is missing?
CMake configuration fails with an explicit error. There are no optional dependencies; everything in the requirements list is required for the build to complete.
Can I use MayaFlux without the JIT (Lila)?
Yes. Lila is part of the engine but you do not have to use it. Programs written entirely in compose() and compiled normally do not touch the JIT path. LLVM must still be present for the build to succeed.
Where does my code go?
In src/user_project.hpp. It is never overwritten by Weave updates. main.cpp is the engine entry point and should not be modified unless you need a custom startup sequence.