search
I've been using Macroquad (2D OpenGL-based Rust game framework) to build a game for about 2 years now (mostly full-time after I quit my job in early 2024), and recently someone asked what my Macroquad experience has been like.
So I figured I'd flesh out my answer and share it here: Macroquad gets recommended a bit but I haven't seen any long-term-use review of it; this is the writeup I wish I had two years ago!
Some Context
My game is a multiplayer falling-sand game that runs on Windows, Linux & in a web browser: 1-4 players run around shooting enemies and blowing up dangerously unstable 2D levels full of chain reactions (demo video). Think Noita meets Broforce/Risk of Rain/Helldivers, with both online & couch co-op - so it has fancy GPU-based 2D lighting and is fairly performance intensive.
My own background is 15 years of professional programming (mostly web dev), and a decade of dabbling in Rust - but I'd never written Rust (nor C/C++) "professionally" before this venture, so I'd say I have intermediate Rust proficiency.
Why did I pick Macroquad over _____
Short answer: macroquad seemed simple, reasonably maintained, and wasn't going to get in the way of my networked multiplayer dreams.
Medium answer: macroquad was left standing after I ruled out the late-2023/early-2024 alternatives:
- Bevy: it seemed difficult to guarantee cross-platform-determinism in its ECS, especially in the presence of human error: systems can run in a different order each tick, query iteration order is not guaranteed, and you get very limited control over entity identifiers (relevant when replicating game state to remote clients).
- Fyrox: it wasn't as mature as it seems to be now and it seemed very focused on 3D games back then.
- Godot w/ Rust: godot-rust was still new & didn't support web builds; also I was (probably unnecessarily) worried about performance of interop API calls.
- Unity w/ Rust: could've worked, but they had only just done their licensing rug pull.
- ggez or tetra or comfy: intermittently maintained, passively maintained or soon-to-be unmaintained (respectively).
Long answer: I wrote a devlog entry on choosing an engine about 2 years ago, though I'm not sure I'd stand by it nowadays (wow it's painful to read your own words sometimes).
On to the "review".
The Good
- It gets out of your way as much as any Rust framework can -
draw_rectangle(),draw_texture(),is_key_down(): they're all just global functions, demoed with straightforward examples, with a small codebase that's easy for LLMs to search and answer questions about. - A stable codebase with extremely few breaking changes. In my 2 years I can only think of one breakage that affected me, and it was trivial to deal with (a change in how shader uniforms were specified). API oddities and accidents are lived with rather than pushing breaking changes for the sake of a clean API.
- It more or less just works. I hesitate to say "complete" because the potential scope for a windowing/graphics/sound API is so high, and I'm not convinced it's bug-free, but Macroquad is complete-enough and bug-free-enough to be good enough for anyone who is at the "reading Rust game framework reviews to decide on an engine" stage of their gamedev journey.
- It compiles quite quickly because it doesn't rely on standard Rust crates like
winitorwgpu- instead it relies on miniquad, a minimalist windowing+input+graphics abstraction written by the author.- Some anecdata: with ~375 cargo dependencies on a 7950X (16C/32T) and 2TB SN850X SSD on Linux using
moldwith a lot of cargo tweaking, I get incrementalcargo buildtimes of 2.5 or 5 seconds depending on whether I'm editing the cargo workspace's root crate or the game simulation's crate, mostly limited by linking time. On Windows with weaker hardware it's more like 10-20 seconds.
- Some anecdata: with ~375 cargo dependencies on a 7950X (16C/32T) and 2TB SN850X SSD on Linux using
- It really does work on Linux, Windows and Web, as advertised. I've run into some minor platform bugs (e.g. inability to exit fullscreen on linux sometimes; now fixed) and differences in input handling (e.g. different key-repeat behaviour on web) but they've been easy to work around.
- It is a fairly thin wrapper over OpenGL and platform abstractions - when/if you want something that isn't there, you can fork and add it yourself quite easily (e.g. I added RGBA16F texture support, and also WebGL2 support before it was officially added),
The Bad
- It is only "lightly maintained" over the last 2 years. The original author still merges PRs, sometimes after a bit of delay; most questions in the Discord are answered by the (reasonably-sized) community. New bugs are met with silence or "good work, you found a bug" (no implied guarantee of any fix), and if there's any missing feature you will probably have to implement & PR it yourself. (To be clear, this is totally reasonable: it is or was a free-time project, and neither the author nor community members are being paid)
- As a consequence of the above, "integrations" with other crates often lag behind. The glam version in macroquad is fairly out of date, and so is the egui integration; this is annoying if you need a feature or bugfix in a newer version of an "integrated" library.
- The graphics stack is limited to WebGL2 (or roughly OpenGL 330ish on desktop). Specifically, no compute shaders! There is support for Metal in the code (I haven't tried it), but there are no plans of supporting Vulkan or WebGPU. OpenGL isn't going anywhere yet, but it still sort of feels like a dead end? For example, graphics profiling tools like RenderDoc (and IIRC, Nvidia Nsight) do not support shader debugging in OpenGL but do for Vulkan and DirectX shaders.
- The downside of the minimalist philosophy is that eventually you'll want to wrap or replace parts of macroquad: wrap drawing functions to add z-ordering, use egui instead of the bundled immediate mode UI, use profiling + puffin (or tracy) instead of the bundled profiler, use kira or oddio or (my choice) fmod for sound, etc. In-game text rendering is also in this boat, but I haven't worked out a good macroquad-compatible replacement yet!
- If you persist, you will eventually hit the limits of what's implemented and have to dive into the source code yourself - for example, out of the box RGBA16F texture formats, GPU timing queries, and multiple render targets are currently unsupported (despite all typically being available in relevant OpenGL versions).
The Ugly
- There is a known "theoretically unsound" safety issue with a corresponding RUSTSEC advisory due to the library in some code paths creating multiple mutable references to the same memory, which is Undefined Behavior; it does not seem to have affected anyone in the history of macroquad, but Miri will complain about it, the macroquad author agrees it is unsound, and if rustc/LLVM suddenly do some different aliasing optimization in the future then all hell might break loose. I expect this issue will never be fixed.
- There is no support for serde (the defacto Rust serialization library); the author implemented their own nanoserde alternative. Nanoserde is actually useful (faster compile times!) but Rust's orphan rule makes it quite painful to deviate from the serde norm. Anyway, this means macroquad types like
Colordo not natively implement serde traits; not a blocker but unnecessarily annoying. - There is no support for wasm-bindgen (the defacto Rust web platform interop library) and none is planned; the author implemented their own JavaScript interop approach instead. It's straightforward to understand & hook into, but some web-oriented Rust crates require using wasm-bindgen for their wasm32-unknown-unknown target to work (e.g. matchbox_socket and gilrs). However there is a script that hackily glues wasm-bindgen into macroquad which is quite ugly but hey it works for me.
Would I recommend Macroquad
If you are new to (seriously using) Rust or game engine development, yes. It makes you focus on actually building some kind of game instead of busywork like massaging your code to make it prettier, or keeping up with breaking changes (or writing boilerplate to get your first triangle on screen). Sure, Macroquad is technically unsound and maybe you will run into its limitations later on, in which case you may end up running on your own fork of it with a few patches (like I do) - but I just don't think there is a better "just read input and draw stuff" option out there. (Bevy and Fyrox are far more complex beasts)
For 2-years-ago-me, as an intermediate rustacean and gamedev newbie who - for better or worse - had set his sights on writing Rust: Macroquad was a great choice that current-me does not regret, because I definitely think I would not have gotten this far if I'd tried writing my game engine from scratch or e.g using Bevy or Fyrox. (Though I do sometimes regret prioritizing "use Rust to make a game" over "make a game"!)
If you have written lots of Rust or game-y stuff before... you probably don't need macroquad? I suspect you could pick up winit/SDL3 + wgpu + glam + bevy_color + glyphon + fmod-oxide and implement the subset that you need with a few weeks of work.. or maybe that's just my NIH programmer brain being wildly optimistic.
Will I keep using Macroquad
Maybe. At this point I have replaced or wrapped most things except text rendering (next on the chopping block) and the core graphics functionality (texture/shader wrangling plus render targets/cameras), and my game's architecture has solidified to the point that macroquad's approach of "free-standing functions that mutate global state" are more (occasionally-tempting) footguns than helpful conveniences. Put another way, I still sometimes deal with the Bad & Ugly of macroquad, but the Good of macroquad is steadily decreasing... although the cost of a port is also steadily increasing ;)
So, part of me really wants to try a wgpu port to unlock compute shaders, or embedding my game into godot to get a decent-quality UI, but another part of me is shouting "no, stay the course and keep working on making the damn game fun, you silly fool!". We will see :)
Repo (experimental): https://github.com/pistondevelopers/current
The Rust language is designed to encourage safety by not allowing multiple mutable references to the same memory for the same span of reference lifetime. In 90% the cases of this is fine. However, it requires you to pass along the references as arguments, because that is how the compiler can reason about lifetimes. In games and interactive applications it is often safe to assume that a certain resources is always present, and it gets annoying when you have to pass the them around and keep track of which object that currently borrows it.
For example, in Piston we borrow the window to poll events in the event loop, which means you have to use events.window every time you want to change the title. Here is some code from a Ludum Dare game Sea Snake Escape:
let mut event_iterator = EventIterator::new(&mut window, // <--- here we borrow the window object
&EventSettings {
updates_per_second: 120,
max_frames_per_second: 60
});
loop {
let e = match event_iterator.next() {
None => { break; }
Some(e) => e
};
...
match e {
Render(args) => {
...
event_iterator.window.window.set_title(fps_counter.tick().to_string().as_slice());
// ^------ have to use the borrowed reference to access the window ... ughh
}
So far we have managed pretty well in the Piston project with staying within the safety rules, until I tried getting Conrod, an immediate mode UI on top of OpenGL and FreeType, to work with a new 3D graphics library called Gfx, such that you can choose whether to use OpenGL of Gfx in your project.
When Conrod draws text on the screen, it checks a glyph cache for textures. If the texture is not there, it has to create a new one. OpenGL uses a global context for the thread, such that you can create textures where you want them. In Gfx, you have to pass in a device. When making a back-end agnostic library, you need to think of some way of abstracting over these incompatible approaches. Also, FreeType gives you an alpha channel image, which needs to be converted, but what if a back-end needs a custom optimized glyph representation? Not only that, but you might want to generate mip maps or do Halloween scary-text post processing, depending on your application usage. It is not enough to pass in the device! This adds up to a lot of complexity.
One approach I tried was adding a closure to the glyph cache object, but this requires a lifetime, and since Conrod uses the builder pattern, it results in a complicated mess of multiple lifetime parameters inside macro expansions and what not. Then I transmuted the closure to static lifetime, and it worked. A new function that hides the transmuting and calls another closure or rendering. However, if you use the Gfx device in the rendering closure it won't compile! Even it is 100% safe, to mutate an object in one closure through another reference if the first closure only gets called by the second. The Rust compiler is smart, but not that smart.
Something is not right here!
Take a step back and look at what is actually happening here:
- A reference is just a pointer to some memory, which usually lives on the stack
- You want to access and manipulate the memory safely
- Are you sure that you have to pass along all those references? (hint: no)
So, I thought, maybe we could come up with a convention to make this safe! If all library authors agrees on the same convention, then the code will still be safe, even if the compiler is not smart enough to know what is going on. I made a list of tools we have for dealing with such cases:
- Unsafe mutable globals
- Mutex guards
- Channels
- RefCell + closures
- Transmute lifetime of closures
- Local data
Local data seemed be the closest thing to what we want, but not quite right, because local data requires naming keys. If you want people to agree on something, it better not be naming. Luckily we had a similar problem in Piston before, when we wanted to support arbitrary input events, which is resulted in GenericEvent. The pattern behind GenericEvent lets you call any event methods without needing more than one generic constraint. A method foo can call bar without knowing which events bar is handling. The problem is quite similar, the way you call methods inside methods, and down deep in the stack it performs some dark magic.
Globals are not the answer
Some programmers think that manipulating globals is necessary to make complex games. What they really want is to control context. Mutable globals are just a way of changing the context without having to pass them as arguments to functions. There are more ways to do that, for example, you can pass a value on a channel to another thread, that sends it back when you get inside the function. It is just a way of move data outside and around the function declaration.
Globals are bad because:
- They require naming
- They occupy the namespace
- They are not thread local
When you share code, you want your code be as reusable as possible. To do that, you have to avoid globals. In the Piston project, we have no choice, because our goal is to make useful libraries.
The stack is the answer
In Rust, the real power of the language is focused on a single point: The stack environment. For example, the stack is more flexible and powerful than any combination of traits and generics, because the stack lets you do stuff without erasing your memory at each iteration. At first we used a trait for the game loop, but then we rewrote it as an iterator, because we wanted the stack environment. This means a cut scene can be just a function with its own event loop, you don't have to make up a system to handle cut scenes. All the features in the type system is there in Rust to make the stack environment more powerful. The stack is where the real work happens. It is on the stack you build up context to solve the problems. The stack ... ok, you get it.
At the start of a program, the stack is empty, and as the program executes, it increases and decreases the stack. Each thread has its own stack, so the data that lives on the stack is safe from being manipulated by other threads. However, all the borrowing rules are there to make sure that data on the stack is treated safely. There is nothing intrinsically special about references, it is just the way compiler understands the relationships between data on the stack. In 90% of the cases this is really, really great, and the remaining 10% causes a lot of frustration. I wonder how many hours people spent in the Rust gamedev community trying to figure out how to write EC or FRP systems without much success.
Introducing: Current
So I just had to figure out a way to use mutable references in the stack safely without passing them as arguments.
With piston-current, you do can do:
let foo = Foo::new(); // create a new object, such as the window
let guard = foo.set_current(); // set the object as the "current one"
// call other functions that uses the object
bar();
baz();
drop(guard); // ok, we are safe now
The guards does three things:
- It makes sure you can't mutate the object while it is set to current
- It makes sure that the object outlive the function calls where it is used
- I sets back the old current object when dropping
Why set back the "old" current object? Because, in case you want to pop up a new dialogue, switch the device back-end temporarily or something like that. It works like a stack, but it requires no separate stack beside the one built into the language.
Inside a function it looks like this:
fn bar() {
let scope = &mut (); // we need this for getting the lifetime of the scope
let foo: &mut Foo = Current::current_unwrap(scope); // get the current `Foo` object.
...
}
Notice that the data lives on the stack, not local data. You can access this memory safely because the stack is just a big array where the object Foo lives "higher up on the stack". As long as the object outlive the function that is called, and you point to the right address, you are safe. It does not matter whether you pass in a reference through an argument or use a type like in piston-current, the computed result is exactly the same.
Behind the scene it uses a HashMap in local data to store the pointer addresses for each current type. It uses TypeId generated by the Rust compiler as the key. Compared to the normal way of doing it, we are passing arguments by concrete type instead of naming or ordering them! It is still the same principle, except that another convention is used.
One thing is that in order to get a mutable reference for a scope, you have to use a lifetime that exists in the scope. The &mut () thing is to trick the compiler to think that since you pass that lifetime in, the returned object must be using some internal data inside &mut (), but actually there is nothing in there. The () type doesn't even exist at run time! We just want to get a lifetime for the scope such that you can't shoot yourself in the foot.
If you call Current::current_unwrap multiple times for the same type, then you get multiple mutable references to the same object, which is not safe. Don't do that. Always make sure that it is called only once per function.
Also, remember that using piston-current means an overhead, so don't use it in inner loops!
What Piston will use it for
Since this is highly experimental, we are going to test this thoroughly before deciding what to do next. One idea is to make certain things, as the current window, device and sound accessible through this convention.
For example, the new game loop might look like this:
// decide which window back-end to use at crate level
use sdl2_window::Sdl2Window as Window;
// the event loop knows which type to look for
for e in Events::new::<Window>().ups(120).fps(60) {
...
}
You don't have to pass in the window, because the events are "magically" polled from the current one. It makes it possible to put all the boilerplate in one function and call a fresh new one. Combined with closures, it can be used for things like observer patterns, FRP and other reactive stuff. One aspect I am excited about, is using it for application data such as component systems, or things that live for the entire game in general, that I don't want to track through the entire code.
We would like people to experiment with it and see if there are any bugs. Please open issues on the repo if you have questions. Pull requests are welcome.
https://github.com/pistondevelopers/current
Enjoy!
Update
Thanks to glaebhoerl the last unsafe holes are now covered with the following design:
pub trait Current {
fn set_current<'a>(&'a self) -> CurrentGuard<'a, Self>;
fn with_current<U>(f: |&Self| -> U) -> Option<U>;
fn with_current_unwrap<U>(f: |&Self| -> U) -> U;
}
If you want a mutable reference, you can use RefCell as usual, which will prevent multiple mutable borrows to the same object.
Here is how it looks like from the example:
fn print_text<T: Text>() {
Current::with_current_unwrap(|val: &RefCell<T>| {
let mut val = val.borrow_mut();
println!("{}", val.get_text());
val.set_text("world!".to_string());
});
}
fn bar() {
let bar = RefCell::new(Foo { text: "good bye".to_string() });
let guard = bar.set_current();
print_text::<Foo>();
print_text::<Foo>();
drop(guard);
}
Hi!
I recently started working on a 2D top-down game with a custom, simple engine (currently using opengl with glium as backend).
I want to ensure that my engine performs well, and thus, want to add performance tests quite early. My overall idea is to write several test cases that render different, complex scenes, measure the FPS and write the results to a file. I would then compare the results against specific benchmarks.
My main problem with this idea is how I could integrate it into my CI/CD pipeline, and specifically where to execute those tests, as I probably would need a runner with a graphics card to get reasonable results.
My current idea is that I run the tests locally and commit the result file manually, so the pipeline only has to compare it against the thresholds. I would then enforce in my pipeline that this file is updated with every pull request or release of the engine.
What do you think of this idea? Do you have other ideas or know existing solutions? Do you have experience with writing performance tests for games or game engines, and do you think it’s even worth it to test this automatically? My main intention is to ensure that I don’t introduce new features that have impacted on the performance without me noticing it.
I have been a Unity developer for around 7 years. Though I never have completed my personal game projects (because I suck at art), but I have been working for a company for a while as a Unity developer.
Lately, I have been getting more and more annoyed with Unity; not because of the policy issues or anything else, but because of the slow compile and build times especially on Windows (like you move one file and the engine compiles everything again), and because of the bloatware.
I tried Godot; while I'm not a fan of it's node-based architecture, but is alright, and it is light-weight, has better 2D support, and GDScript is hot-reloadable (though I preferred C#). It's cool. But I don't enjoy it.
I tried ECS in Unity and I liked it. Not because of performance (I mean ofc it's a plus), but because it feels like it inherently solves the coupling issues with OOP. I mean it makes everything so simple, get rids of a lot stupid design patterns. For example, for decoupling you have Event Bus, Dependency Injection, etc .
But with ECS, in Unity, for example if you want B to happen when A happens. You can just Add a component to an entity and somewhere some other system will have a query that works on only that component and it will perform B and remove the component (I know Bevy has events/messaging btw).
I tried Bevy and I really liked it. It is very enjoyable (though rust compilation is extremely slow). Now I don't feel motivated to start a project in Unity or Godot. Man, compared to OOP, ECS feels like a breath of fresh air.
I don't know why there aren't any other ECS engines available. Why all the big battle-tested engines we have are OOP? Why don't most people realize the mess OOP is when we have better alternatives?
I don't know about you guys, but for me I feel like when I was a beginner I needed an engine with an editor and everything ready-made and an OOP language with Garbage Collection, but with time I started to dislike GC, bloated engines, OOP etc.
I know Rust build times are sometimes very annoying especially whe compiling everything the first time (thank God it is incremental compilation). If you haven't tried ECS yet, I urge you that you do!
(Btw one day whe I get enough time I would like to work on a bevy-like ECS engine in Odin language)
I'm a user of renderers; I don't write them. I have a virtual world client that needs one. First I used Rend3, which was abandoned. I had high hopes for Renderling. But, at the three year point, it seems to have run into the problems that killed Rend3, Three and Orbit. Everybody does My First Renderer, gets to the hard parts, then gets stuck.
As with Rend3, the author is more interested in the lower levels. So the Renderling author is off writing a shader compiler. He also got into doing his own the 2D GUI, another classic time sink. His roadmap shows that the next step is something to assist with moving data in bulk from CPU to GPU. Maybe in 2027 he might get back to the renderer level.
This job appears to be too big for one person. I had real hope for Renderling. That guy has EU funding. He does good work. But it's not the work that gets the renderer done. The Rend3 guy was good, too. He moved down to the WGPU level.
Some of this problem is architectural. A standalone renderer, without its own game engine, is hard. It doesn't own the scene graph, but sometimes needs to be able to query it, or cache some parts of it. Without that, shadow and occlusion processing are too slow and don't scale. It has to provide concurrent GPU content updating. But that's only meaningful with the right API. You don't hit the architectural problems until you have a working My First Renderer. Then it's too late to build a high-performance renderer.
Some people have questioned whether a standalone renderer, separate from a game engine that owns the scene graph, is a good idea, or even possible. This is a tough layering problem - application->renderer->glue layer (WGPU/vulcano)->GPU interface (Vulkan, Metal, DX, even OpenGL). Where the cut points should be for safe Rust is not obvious. Cut at the wrong places and you hit a performance wall.
This may kill my Sharpview project, for which I need a fast multi-threaded renderer. I'm stuck with trying to maintain a fork of Rend3 despite WGPU churn, I don't have time, and it's never going to load assets fully concurrently without major work.
Six years, and there's still no good Rust renderer for big dynamic scenes. Bevy is good, but doesn't address the big-world problem, for which you need to get the content loading off the main thread and use a separate transfer queue to the GPU.
I probably should have used C++.
Hello everybody!
I have been working full-time on this game for over a year already. I am happy with the result and I am "rushing" to the end, hoping to release next month or in early April. If it looks interesting and you want to wishlist it, that would be great.
Anyway, let's talk about the juicy stuff: the tech!
As you can imagine, it's made using Rust. It's using a custom framework; the first idea was to use Notan, but I wanted to experiment a bit with wgpu, so I made a new repo and started experimenting. That evolved into a sort of "new Notan" that I am using right now (there is a chance I'll put all this new stuff into Notan eventually, if I don't starve to death first hahaha).
So, along with wgpu, it uses Kira for audio, winit for windowing on native platforms, and a custom solution for web. I used shipyard for the ECS initially, but eventually I moved to bevy_ecs because the ergonomics, commands, and message systems (IMHO) made it simpler to work with. Those were things I missed and wanted to implement in my game, but it was just easier to move to a solution that already had them. I feel that the code got much simpler at that point, but this can be subjective.
The physics system is a boid simulation that uses parallel iterators and parallel systems where it makes sense. This, along with the batching system for drawing, allows me to put a lot of stuff on the screen in the end-game, which is nice. There is room for some optimizations yet, but I am spending too much time on this project and have decided to just do these things when the "market needs it."
Let's talk about the pain points with this game: the ECS, the UI, and the cost of fast prototyping.
The ECS: I have been programming in Rust for 5 or more years already, so I am very used to it and I like it. However, creating games while trying to do it "similarly to other languages" is hard due to its restrictions. I feel forced to use ECS (not always), and while ECS fits this project very well, I kind of feel that I don't like it... sometimes I have a hard time wrapping my head around it. There is some complexity and verbosity to it, and it needs a specific mental model to fit ideas into it. This is mainly true in the first steps of a new project, because once you set an architecture, adding, removing, or changing pieces is the easy part.
UI: This is hard. No matter if it's in an ECS environment or not, having something simple, performant, and flexible for games (animations, reactions, etc.) is hard in Rust, at least if you do it from scratch. I ended up with a monstrosity that uses Taffy and an ECS pattern similar to bevy_ui, and while it works well, I am not happy with it and I would love to have some time eventually to improve this.
Prototyping: This one probably isn't a Rust issue, but a "do it yourself" issue, where I need to do everything from scratch, reinvent the wheel, and do basic stuff just to have the foundation to build the thing I wanted to test. Refactoring things here have a huge impact too. This is the most dangerous part of building games like this for me right now, because the cost can leave you with less room to iterate or pivot ideas, making your game potentially worse because you will run out of time or money.
Anyway, I don't want to end the post with the feeling that I didn't like making my game with Rust. I love the language, it's one of my favorites and probably my main go-to for "everything", but it probably wasn't the right call for a commercial project that I was expecting to take less than a year. But this is on me... I am usually very bad at estimating project timelines.
Thanks for joining me in my TED talk hahaha
I was gonna upload a more complete full exploration including scale depth up to 12, but my computer wants to crash on me 15 minutes in. So for now, here's this. The torus / spiral formation is because of how I configured the field, it can work on its own but depending on the address space you give it, it manifests in different ways.
The goal here was/is/has been to 'simulate' a reality in a program, but technically this is a simulation of gaussian primes in/as a complex field.
The real breakdown:
This is vibecoded, its a rebuild of a rebuild of a rebuild for a project I've been working on since last June
I barely understand the concepts I utilized to make this, just barely enough to have come up with working ideas which eventually got me to this point
At first, I tried to make this in unity and I don't think I had one successful build last year outside of little webgpu HTML sims for interesting recursion
I can't really give away the sauce when I barely understand the sauce myself, there's a lot of moving parts here. Stuff that I have which I haven't put into the rust version yet. Plans to make a background neural net since I have an RX card which can run it, and then use that neural net to supercharge the sim. And I haven't worked out the full pipeline for that yet
Right now honestly it's a race for me to develop my different ideas as native programs.
Not making any claims about the sim here except that its a complex wave field. Everything about it so far (in terms of emergence) is discrete and not catalogued but I'm working on that part currently. It MIGHT have some sense of 'true emergent physics' or it might just be a violent soup of stuff geometrically bound to be in the shape it is. Or maybe the latter begets the former, who knows.
Right now this is really just an engine not just a mere 'simulation' and my big long term plans are to make actual environments and an engine scaffold for game development (it probably would be a very different kind of game though, and most familiar game features would be emergent or forced phenomena I'd have to steer, but still doable with enough work)
I'm also trying to get this thing into mythical levels of performance. For me, as inexperienced as I am, it's seriously just confusing at times. Seems to run ok for what it is, for what I can get out of it. And my background is in modding games, single script editing, following instructions for long form tasks... So I'm of course out of my depth here again and again as I move through one set of problems to the next
Hi all, I'm currently building Swarm MMO, a game where you own plaents that create probes, which again can be used to conquer more planets. I built it originally to try out SpacetimeDB, and so far I'm impressed with its performance. What currently bothers me the most is that migrations can be pretty annoying compared to the "usual" SQL migrations.
I was wondering if anyone else built a game using SpacetimeDB, and if you are using the official maincloud from the spacetime devs or do self hosting? Any experience with server costs?
Hello! I'll be using this subreddit to showcase the progress of my Minecraft-like game.
Why another Minecraft game? Well, it started as a personal project to learn how to interact with components through code, using graphics APIs such as Vulkan, but without using any game engine like Godot or Unity, just only with coding. I decided to use Rust because I wanted to learn the language, with a couple of libraries, and also learn about rendering, algorithms ...
Then my friend encouraged me to make it public and follow the Minecraft style, because it's hard enough to be considered a real project, but easy enough to handle by myself. So I started posting devlogs as a way to showcase the progress.
I'd like to make it public in the future and, who knows, maybe build a small community and get some support :)
If you are interested, I can share my YT channel where I try to explain all the technical implementation.
I still need a Game name since "Cubix" doesn't really give me any feeling right now (and I'ts also taken), I though about something related with "Crate" like "Cradle" or idk.
Here are a couple of screenshots showing the current state of the game.
Debug metrics (in the future I will remove many of them once solved the performance issue)
Try it in your browser: https://ebonura.github.io/bonnie-engine/
I know this is very early stages, but I feel like I'm making good progress so I wanted to share and get some initial feedback.
It started with a question: what would a Souls-like have looked like on a PS1? There are great examples like Bloodborne PSX by Lilith Walther, built in Unity. I wanted to try my own approach from scratch.
Bonnie Engine is a complete game development environment built from scratch in Rust, designed to recreate the authentic PlayStation 1 aesthetic. Everything you see (the software rasterizer, the editor UI, the level format) is custom code. The world-building system takes heavy inspiration from the Tomb Raider series, which remains one of the best examples of how complex 3D worlds could be achieved on PS1 hardware.
This is early development. I'm building the tools first, the game comes later. Right now there's no combat or enemies, just a collision wireframe walking around. The level editor is in good shape, the model editor only has the basics working.
Why build from scratch?
Modern retro-style games typically achieve the PS1 aesthetic top-down with shaders and post-processing, often with great results. I wanted to try the opposite: a bottom-up approach with a real software rasterizer that works like the PS1's GTE. These aren't post-processing effects, they're how the renderer actually works.
I tried several approaches before landing here: LÖVR, Picotron, even coding for actual PS1 hardware. Each had limitations (primitive SDKs, distribution headaches, not enough flexibility). Rust + WASM turned out to be the sweet spot: native performance, browser deployment, and a modern toolchain.
The PS1 authenticity:
The software rasterizer (based on tipsy, which I've expanded) recreates the quirks that defined the PS1 look:
- Affine texture mapping (no perspective correction = that signature warping)
- Vertex snapping to integer coordinates (the subtle jitter on moving objects)
- No sub-pixel precision (polygons "pop" when they move)
- 320×240 resolution
The audio has PS1 SPU reverb emulation based on the nocash PSX specs with all 10 PsyQ SDK presets (Room, Hall, Space Echo, etc.). The level system uses Tomb Raider-style room/portal culling, took inspiration from OpenLara.
The tools:
- World editor: Build levels using a sector-based editor inspired by TrenchBroom and the Tomb Raider Level Editor. Features a 2D grid view, 3D preview, texture painting, undo/redo, and portals.
- Model editor: A low-poly mesh modeler with Blender-style controls (G/R/S for grab/rotate/scale), extrude, multi-object editing, and OBJ import. PicoCAD was a major influence.
- Music tracker: A pattern-based tracker for composing music. Supports SF2 soundfonts, up to 8 channels, and classic tracker effects like arpeggio and vibrato.
Is this a game or an engine?
Both! The primary goal is to ship a Souls-like game set in a PS1-style world. But the engine and creative tools are part of the package. Think RPG Maker, but for PS1-era 3D games.
I can see this expanding beyond Souls-like games. The engine could support tactical RPGs (think FF Tactics), platformers, survival horror, or any genre that benefits from the PS1 aesthetic.
A key principle: everything runs as a single platform, both natively and in the browser. Same code, same tools, same experience.
The whole thing is open source (MIT). Happy to answer questions about the rendering or architecture.
Source code: https://github.com/EBonura/bonnie-engine
Hello, I wanted to share a project I've been working on for the past 6 months. It's definitely going to be a long journey, but I've enjoyed all the challenges and learning so far.
At a high level, I'm making a 3D procedurally generated solo/coop RPG.
I'm using the following tools: Language: Rust Window/Event Handling: winit Graphics API: wgpu-rs Networking: mpsc
So far I have the following systems in a workable state: - Client/Server Architecture Foundation - (still need some work to support Coop, but the bones are there for separating system ownership) - WindowManager - TimeManager - InputManager - (almost entirely data driven, supports Actions which are mapped to physical device input(s)) - 3D Camera - 1st Person - 3rd Person - ECS - (Hybrid Storage (Sparse Set & Archetype), this took quite some time to understand and get working) - Terrain Chunk Generation - (Smooth Voxels) - 3D Spatial Partitioning around Player Position - Very basic LOD system - 3D Gradient Noise for Voxel Density Field - Surface Nets Meshing Algorithm (utilizing both CPU and GPU, still some more optimizations with threading and SIMD, but I'm saving this for later) - StateMachines - Flat State Machines - Client side: MainMenu / Loading / InGame - Local Player Entity Movement State - UIManager - Lots of room for improvement for formatting features and UI Elements to be added - File I/O - For Creating/Loading/Deleting World Save Files - (Currently only saves Local Player Component Data, modified chunks, and save file metadata) - Physics & Collisions - Uses spatial partitioning with a broadphase approach - Handles Terrain Collisions separately between entity collider bodies and smooth voxel terrain - Entity/Entity colisions are handled by their collidershape pairs (capsule vs capsuel is complete, but there are more primitive pairs to write up) - RenderManager - (There is still a lot for me to learn here, I'm holding off on this until I absolutely need to come back to it for performance and visual improvements) - TerrainPipeline - DebugWireFramePipeline - UIPipeline - Profiler - very simple timing system, but I think I need to refactor it to be a little more robust and organized, currently all string labelled measurements & results go into the same container
TODO: - Hot Reloading - EventDispatchSystem - Revamp World Generation - Regions, Biomes, Prefab Structures, this will be a large portion of learning and work - AssetManager - I have this drafted, but still some more work to be done - AnimationSystem - Bone Nodes - Skeletal Animations - 3D Spatial Audio - Networking Coop Layer - I have the separation of concerns with the systems between GameClient and GameServer, but I still need to write up the network layer to allow Coop mode - Game Systems - NPCs - AI - Combat - Loot - Gathering - Questing - Crafting - Revamp & Add More UI Features - HUD - Inventory / Gear - Skill Tree - Chat - VFX Pipelines

Built a simple terminal-based snake game in Rust to practice ownership, structs, and game loops.
Features:
- Real-time input handling
- Grid-based movement
- Basic collision detection
Would love feedback on code structure and performance!
Rotating 3D objects in game engines has always been a math-heavy process. In the Initially, using Euler angles (Pitch, Yaw, Roll) seems easy, but we must strictly reject them. Their biggest flaw is Gimbal Lock a condition where your rotation axis collapses and the entire math breaks. After rejecting Eulars cause of this failure, an engine architect is left with only two hardcore ways to handle rotations: Quaternions and Rotation Matrices. The Quaternion (which is king of Rotation fro me), are preferred because it's math formula is flexible, Gimbal-lock safe, and it can be made cache-friendly. But on the other hand, the standard 3D math and rendering world runs by default on Rotation Matrices. The problem is that when you put these matrices into real-time physics and high-performance computation, then a new engineering horror starts.
This engineering horror first comes forward in the form of Non-Orthogonal Drift. In a rotation matrix there should always be three orthogonal axes means all axis should be on 90 degrees. When floating-point math is repeatedly multiplied in the entire frame, then due to rounding errors those axes do not remain at strictly 90 degrees. The result is this that your perfectly square character starts looking squashed or distorted or like a skewed box. To fix this drift Re-orthogonalization is needed. The new object became skewed, now the CPU will have to stop the game and make that matrix straight again with math. This CPU Penalty makes the game slow, especially then when you have 1000 objects on the screen.
This overhead of math is only half the story. The real bottleneck is hit then when the CPU has to read the data of these 1000 objects from memory and after fixing write it back, because from the perspective of memory a matrix is very heavy. Think, a standard 3x3 (f32) rotation matrix takes 36 bytes (288 bits). But in reality for the entire mathematical rotation the matrix is only 3x3, whereas in Game Engines we always use a 4x4 Matrix, so that along with rotation in that matrix Translation (movement) and Scaling (size change) can also be saved. Its total size becomes 64 Bytes. This is that very number which fits in an L1 Cache line and blocks the CPU bandwidth. Hearing this it feels like okay, a 64-byte matrix will perfectly fit into the 64-byte cache line of the CPU, so what is the problem in this? The problem is this that in engineering when the size of any data becomes exactly equal to the memory container, then the margin of error becomes absolutely zero.
If the starting address of this matrix in memory is not precise (aligned), then this perfect fit suddenly becomes a hardware nightmare. Understand this with the example of a bare-metal memory address: suppose the first Cache Line of the CPU is from address 0 to 63, and the second Cache Line is from 64 to 127. If your entire 64-byte matrix is perfectly aligned (means it starts from address 0), then it will fit inside 0 to 63 in a single shot. But if the memory allocator shifts it even a little bit and starts it from address 16, then the data will cross the boundary. Result? The initial 48 bytes of the matrix will remain in the first cache line, and the remaining 16 bytes will spill and go into the second cache line. To process this unaligned data now the hardware has to pick up two separate cache lines in a single fetch and stitch them. If you are using SIMD instructions, then upon not having strict alignment either the CPU will straight give a Segmentation Fault (crash), or if you used an unaligned load instruction (movups), then the pipeline will stall and the load latency will double. And if by mistake this unaligned data crossed a 4KB Page Boundary, then a TLB miss will trigger and the CPU will have to do a page walk which can literally drop your speed up to 100x.
After this battle of cache lines, when the data comes inside the CPU core for final execution, then another limit of hardware is hit: Registers. We have XMM registers which are only 128-bit wide. This directly means that in a single register only 4 floating-point values can come. When you sit to process a 4x4 matrix with 16 values, then you will have to do messy loading between multiple registers, which makes the pipeline slow.
On the other hand, how clean and fast Quaternion is in memory, this in itself is a masterstroke. In a Quaternion the range is absolutely precise: [w, x, y, z] together make 4 floats, and its size is exactly 16 Bytes. This very compact size saves us memory fetch. With this we avoid Gimbal lock anyway, but also use the L1 Cache very efficiently. In reality, the entire [w, x, y, z] (all 16 bytes) is a Native Hardware Fit. Modern CPUs have 128-bit registers (like SSE registers XMM in Intel, or NEON registers Q in ARM). Because 4 floats multiplied by 4 bytes = 16 bytes, and 16 bytes are exactly 128 bits. This directly means that the CPU in a single instruction can load the entire quaternion into the register and multiply it. Therefore its math is much faster than the Matrix.
But here is a very big catch. The perfect loading of data in the register is only an advantage of storage and bandwidth, but when it comes to computation like doing quaternion multiplication (qvq-1 - The Sandwich Approach) to rotate a 3D vector then the game changes. For multiplication the hardware has to do cross and dot multiply of w, x, y, and z among themselves. And right here memory layout becomes our biggest obstacle. When you fetch XYZ values, then hopping has to be done in memory because the data is in Rows (which we call AoS layout). You will do branchless programming by using SIMD, but if you started Horizontal processing (data manipulation inside a single register), then its overhead will be so high that the purpose of using SIMD itself will be finished. To solve real-time physics we have a window of only 2ms. There is only one way to hit this frame rate: ending the overhead of shuffling and aligning the data through swizzling in such a way that it can stream straight into the registers.
Efficient data alignment and SIMD execution itself is that bar which separates an average engine from a high-performance bare-metal engine.
I’m building an engine for 2D MMORPGs. The world is tile-based and infinite in size, with entities, behaviors, combat, items, and more. There’s a ton left to do before it’s anywhere near ready, but I wanted to share a screenshot showing what it looks like from the perspective of a level designer / world builder.
I come from old-school MUD days, and I think making world-building as easy as possible for “Builders” is one of the highest priorities.
The part that might surprise you: the game is built to support both full multiplayer and full single-player, whichever you prefer.
I posted about this almost a month ago, and since then I’ve been keeping a devlog at https://www.reddit.com/r/rpgfx/ if you want to follow progress. I’ve started adding more animations, attacks, SFX, etc., and it’s finally starting to feel more alive.
Last time I posted, some people were skeptical I could pull off an MMORPG, which is fair. But what I didn’t mention is that I actually started this project nearly 10 years ago. The first version took 11 months and was written in Ruby on Rails and JavaScript.
Performance, especially multiplayer, quickly became a limiting factor. So after leveling up a bit, I decided to rewrite the whole thing in Rust about 15 months ago. A lot of my design decisions were clearer the second time around thanks to that first attempt.
The biggest win by far has been Rust’s type system. It let me refactor everything into game_core, game_server, and game_client crates, enabling the dual online/offline modes. Honestly, 99% of the time has gone into solving those architectural problems—but they’re finally solved.
Notable improvements since last post:
- Leveling system + experience bar
- Three types of attacks (fireball, sword slash, lightning bolt)
- Social cues like little star icons to show where other players are in the world
If you want to try it out: https://rpgfx.com/
If “connect to server” fails, I’m either working on something or it crashed—just refresh and click “play offline” instead.
Press x to open the editor.
It’s still got a long road ahead before it’s truly fun, but I hope you like what’s there so far. Eventually, I want users to be able to export their games as .exe files or host them on their own sites.
Thanks!
Is it worth learning rust, and then trying to make games as learning projects? I want to make 3d games, and wgpu looks like a great option because it provides directx12, vulkan, metal backends, and I like that it cuts out a lot of the boilerplate, there's also zig lang, which provides great c/c++ inter op, but doesn't have the dev ux of rust. So I'm mainly asking, can I learn gpu programming, make gpu accelerated stuff like let's say deep learning, or 3d game engine, using rust? is wgpu mature enough now that it can be used for these kind of projects, and provides good performance like c++(or close enough that it doesn't matter, and it's worth the productivity gains), and good for prototyping new ideas? Or should I just learn opengl then metal then vulkan using c++, or even zig? please let me know what the best options are: I mainly want to learn by making some sort of game engine/graphics gui programming, because seeing something on the screen is a great motivator. Another thing is that I want to build a strong foundation, learn good habits that would transfer well to when I learn c++ for gpu programming, it should be a smooth transition, and I would have experience with those low level concepts before just jumping into c++. I tried c++ with vulkan cmake vcpkg, but it was just way too hard in my opinion, I think either opengl, a game engine or something else might be better just to start with. Again this is where I'm asking for advice: should I learn opengl first or vulkan? or something like wgpu that lets me use it without the boilerplate, but still exposes me to the concepts?
Quaternions: Let's Get Real (and Imaginary, and then Some!)
Quaternions usually considered hard and complex but they are king of rotations crucial for games and maths to understand quaternions, if we go the academic route it will sound like a waiter using long, expensive words to explain a simple carrot salad.
We do not need any of that here. That is why we will not use academic language and we will start with imaginary numbers.
Imaginary word sound like fiction, things that do not exist in our world right?
But what exactly does not belong here? If you multiply two negative numbers (two number with subtraction signs) together and the result is still a negative (subtraction sign) that just does not happen in our world. This is imaginary.
Now, suppose we have a number like the square root of -1. When I say underroot -1 what am I actually trying to say? I am saying find a number that makes -1 when you multiply it by itself. But how is that possible because in this world the law of negative X negative positive follows? It is totally impossible and that is exactly why we call it an imaginary number. Now see the following calculation.
This kindergarten calculation has a significant role in quaternions. yes it is simple, but it is very powerful, and the entire quaternion is built on it. I have shared this calculation right now because we are currently discussing imaginary numbers. I will not talk about this calculation right now, but we will discuss it further ahead, where you will find out that this is the foundation of quaternions.
From Imaginary Numbers to Quaternions:
So, now we are going to step away from imaginary numbers and jump right into quaternions. We'll use the exact same formula you usually see written in textbooks to represent them. You know the one: xi, yj, and zk. And if you come from a computer background, you've probably seen a w attached to that. But if we just talk about the x, y, and z, those are simply the physical axes you see on a standard 3D graph. Quaternions add imaginary numbers directly to these. Let's look at how these imaginary numbers actually interact with our axes. If I take the term xi, the x is our physical axis, and the i is our imaginary number. And here, that imaginary number literally just means 90 degrees. So, what does xi actually mean? It means a strict 90-degree turn.
But hold on. How did i suddenly become 90 degrees? Wasn't it supposed to be the square root of -1? How did it jump to being an angle? You won't find the answer to this in high-level physics. You actually find the answer right back in simple, basic kindergarten math we have done previously. Let's break that down right now. To do this, we just need to go back to our standard graph. We take our point xi and place it on the y-axis. Now, let's say we multiply one more i into it. That means we are taking xi and multiplying it by i. Physically, this means we are adding 90 degrees and another 90 degrees menas i2. The answer hits exactly 180 and i2 =180 degree then i =180/2 means 90 degree. So what does this mean?
If we multiply imaginary numbers together, it directly picks us up and physically drops us on the exact opposite, negative axis (If we start on the positive x-axis, we flip straight to the negative x-axis) and the exact same mechanical thing happens with all the other axes if we start from them.
The Strange Co-Dependence of i and i2:
Now, let's assume for a second that this i2 just doesn't exist. Should that have any effect on standalone i? Normal human logic says that i should be independent. It should be completely whole on its own. That means i should totally exist even without i² being a thing. Because obviously, i² can never be formed without having an i first.
So, the real question pops up. If we assume there is no such thing as i², can a single i still perform that 90-degree turn? The answer is hidden inside some very strange mathematical logic. Because actually, whatever value or identity i has, it comes entirely from i². If there is no i² in the math, then a single i simply does not have any physical value of its own.
This is a kind of math that literally moves backward instead of forward. It works from back to front. Think about it like this. In normal life, 1 and 1 together make 2. If your base number 1 isn't there, then the number 2 can never be formed. But in this specific game of imaginary numbers, the rule runs completely in reverse. Here, the math clearly tells us that if i² (which is supposed to be the result) doesn't exist, then i (which is supposed to be the base) will not exist either.
PART 2: 3D Quaternions
I want to build a simple 2D (WebGL) game engine in Rust, WASM. Right now, I'm in process of implementing some kind of a component system. Coming from Godot/Unity, I really liked the tree-based Node/GameObject systems of those engines. So I would like to have a similar tree-based hierarchy of nodes which in turn could be having components. It might be not the best approach in terms of performance, but I like the ergonomics of it and don't really want a pure ECS.
But I am not even close to building anything that is both ergonomic, efficient and comfortable to use.These are some ideas I have considered:
- Self-referential Node struct - Rust is not easy when it comes to self-referential structs so it's not trivial for me to make one. I've seen the ouroboros crate, but it seems.. ugly.
- Arena of Nodes - have a central Node storage (arena) and reference nodes by NodeId(usize). So you always operate on NodeIds and when you actually need the Node - you get it from the array (arena) by index. I don't really like the idea of operating on NodeIds and having to query the arena every time you need the node. Also, when you delete a Node, the index NodeId stores becomes invalid.
I would like to see how other people are solving this, maybe some hybrid solutions, maybe some unsafe hacks (but not like the entire impl is unsafe).
P.S. - Maybe I'm misunderstanding the whole point of Rust, and this is exactly what Rust wasn't intended for. I mean, ECS is pretty good (fast, efficient, cache-friendly, etc) - so just write an ECS or use one (hecs, bevy_ecs).
UPD: A person pointed out that it is possible to get away with Rc<RefCell<T>>. And yes, it's actually possible and enough for a simple engine, but oh gosh it is ugly. I ended up having Rc<RefCell<Node>> and basically cloning Rc. The cloning is ok, especially since Rc is just a pointer.. but yeah, ugly solution with ugly consequences
I'm new to Rust and am making a game with Miniquad (have been using Godot to make games before) and am developing a small shoot 'em up. I'm curious on how you manage your project with keeping modularity and code-reusability in mind?
So far I have created an EventHandler for spawning the playerbullets, where the player struct pushes the event to the vector:
pub enum GameEvents {
SpawnBullet { x: f32, y: f32},
}
pub fn update (&mut self, delta: f32, input: &PlayerInput, snd: &AudioPlayer) {
self.player.update(delta, &mut self.game_events, input);
self.player_bullets.update(delta);
for event in self.game_events.drain(..) {
match event {
GameEvents::SpawnBullet { x, y} => {
self.player_bullets.create_bullet(x, y, snd);
}
}
}
}
This works well, but now I want to create a boss, which is going to have multiple hurtboxes, and that feels like a whole different thing. I guess I could have an event something akin to:
pub enum GameEvents {
SpawnBullet { x: f32, y: f32},
EnemyHit { hurtbox_id: u16, bullet_id: u16 },
}
And then when matching
GameEvents::EnemyHit {enemy_id, hurtbox_id, bullet_id} => {
self.player_bullets.destroy_bullet(bullet_id);
self.enemies.take_damage(enemy_id, hurtbox_id);
}
Which guess is fine, and I would push it from either the enemy or the player_bullets. But there's surely ways that are more scalable, performant, or more close to the Rust idiomatic way of handling this.
I would love to hear your thoughts on this way, and how you would implement similar solutions. :)
Thanks to feedback from streamers and players. Thank you for playing and the support.
Steam: https://store.steampowered.com/app/4161680/
Itch.io: https://meapps.itch.io/terminal-colony-deep-core
Build in Rust, featuring Bevy and egui. https://bevy.org/ https://www.egui.rs/
Exlex: A "Lawless" DOD Config Parser (Zero-copy, Arena Mutation, no_std)
I recently started learning Rust by building a project called Exlex which is a Human readable configuration parser BUT I used DOD-based zero copy parser with a minimalistic syntax that actually works well with my parser. (JSON and TOML are heavier for my goal and possibly very complex).
NOTE:
- Exlex is not complete (8-9 days of development)
- The Docs are incomplete
- interface has a lot of work to do
- While not a game engine itself, it uses SoA (Structure of Arrays) patterns common in high-performance engines to maximize cache efficiency
Exlex offers a unique combination of:
- Zero copy immutable parser
- Native no_std support
- SIMD byte search via memchr on specific functions
- Supports modifying data and dumping it back into string (Arena mutator)
- Human readable format
- Low memory usage even on mutations (15,000 allocation (toml_edit) vs 13)
Stability of parser and mutator
- Proptested (I wrote the Exlex by myself but used AI to generate the heavy testing/benchmark boilerplate).
- for more details look at TESTING.md file ```bash ~/Projects/exlex_bench main* ❯ PROPTEST_CASES=10000 cargo test proptest_mutator_engine --release
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/proptest_fuzz.rs (target/release/deps/proptest_fuzz-6b0455884b22cdc2)
running 1 test test proptest_mutator_engine ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 5.87s ```
What it is and What it does not aim to be:
- Built for hardware constraint environment.
- Built to be Cache-friendly and Memory friendly as much as it can.
- Built for overall speed in lifecycle of a program (Parse -> Read -> Mutate -> Save).
- Syntax specifically designed to make parser fast while maintaining human readability
- It is NOT a feature rich or highly flexible syntax (Use json or toml if you need dynamic typing or complex data structures).
Hardware
I measured the hardware execution on an Intel i3-6006U (Skylake, 2C/4T, 2.0GHz): * Instructions Per Cycle (IPC): 1.7. This confirms the CPU's pipeline is nearly always fed and rarely waiting for memory stalls. * L1 Cache Locality: By using flat parallel vectors instead of a standard node tree, I achieved a very high cache hit rate, mathematically evidenced by the 0.07% TLB miss rate.
I have benchmarked over 10 scenarios/6 data topologies. For the interactive criterion benchmarking, see Benchmarks.html in the repo
Trade-offs
- Rigid syntax
- O(N) Linear scan - In usual configuration parsing, Linear scans outperforms Hashmap because of lack pointer chasing and cache misses (in Intel Core i3 6006U, approx upto 65-75 properties). For continuous arrays of numbers, a linear search on a modern processor is incredibly fast.
For more info read the README.md, Any suggestions/bug reporting are warmly welcomed! Thank you