Sharing Saturday 300

· ☕ 6 min read

Good luck with 7DRL, everyone! I wish I had time to participate this year, but that set of stars is far from aligning. Winter is thawing, which makes my day-job quite busy (it involves lots of radios mounted on roof-tops, so the deep-freeze lets us hibernate a little). The ink has dried, and I’m now a fully contracted author - with an NDA, so at this point my ability to write progress updates on that front is rather limited. I’ve been focused on the library side of things.

RLTK_RS - Roguelike Toolkit for Rust | Github | Crates.io

It’s been a busy two-weeks on the RLTK front! RLTK is heavily used in the book I’m writing, so I want it to have a very friendly/good public face. I’m also collaborating with the author of doryen_rs to include everything that his library can do, allowing his project to use mine.

Breaking up into pieces

RLTK has been surprisingly successful (I didn’t expect to be in the thousands of downloads category!), and that leads to quite a few requests for improvements. One frequently requested item was a way to use parts of RLTK, without necessarily pulling/requiring the whole thing. It’s also become apparent that people are using it for far more than just Roguelikes! So: bracket-lib is born. There are now a family of crates from which you can pick/choose functionality:

  • bracket-color is an RGB/HSV library.
  • bracket-geometry is back-end (decently high performance) geometry handling - lines, circles, points, rects, that kind of thing.
  • bracket-noise is a port of Auburn’s FastNoise to Rust.
  • bracket-pathfinding provides a really high-performance A* implementation, and a decent Dijkstra implementation (optionally threaded).
  • bracket-terminal is the CP437 and game-loop management system. Now supports: OpenGL, Web Assembly, Curses, Crossterm (a really awesome cross-platform terminal system a bit like curses but with more emphasis on native code calls), and Amethyst (the full scale engine!) as back-ends.
  • bracket-lib gets you all of the above, neatly wrapped.
  • rltk remains, but is a wrapped around bracket-lib - with namespace/item renames to ensure that all your existing RLTK code still works.

Improved Initialization

The initialization system was decent, but not very canonical Rust. A PR helped me get started with this, for which I’m eternally grateful: I was about to do it anyway. :-) The setup system and main loop now handle Rust’s error propagation system. So a minimal setup would look like this now:

1
2
3
4
5
6
7
fn main() -> BError {
    let context = BTermBuilder::simple80x50()
        .with_title("Hello Minimal Bracket World")
        .build()?;
    let gs: State = State {};
    main_loop(context, gs)
}

main returns an error type, allowing you to use ? anywhere instead of having to type out .expect("It didn't work!") all over the place. That’s a minor change to existing code (the tutorial will be updated concurrently with this going live as a crate version) - but a decent win in terms of fitting in with the Rust ecosystem.

Back-end separation/access

I’ve not been entirely happy with RLTK’s habit of passing around an entire back-end as part of the context object (to which it hands you access on each frame). It leads to some dancing to make sure that you aren’t locked out of access by the borrow checker - in some cases, quite a lot of code that isn’t really needed. In turn, that made it hard to actually exploit the back-end directly if you wanted - because RLTK had its little claws in it, all the time.

So the context object you receive on each frame no longer includes the back-end. Instead, there’s a (mutex locked) static through which you can request access in a thread-safe fashion. Thread-safe with the caveat that the back-end itself may not be safe against you using your own threads (OpenGL isn’t, Vulkan is) - so if you want direct back-end access it’s up to you to make sure that the actual platform access occurs in the main thread if necessary. It also implements an optional callback (GL mode only for now) that runs after the console is drawn - allowing you to add native GL to your rendering.

This made the context a LOT more lightweight, for a decent speed-up (even in debug mode). It also allows things like this, which includes native OpenGL/WebGL calls inside an RLTK program.

Input Cleanup

RLTK offers a pretty simple input system. That’s good - and nice/easy to teach - but quite limiting if you are writing something as big as Nox Futura or the doryen_rs game. I wound up with four goals:

  • Maintain compatibility with all the existing code.
  • Offer a new API, with is_key_code_pressed, is_scan_code_pressed, is_mouse_button_pressed, mouse_pixel_position and mouse_tile_position support.
  • Provide an optional (opt-in) event queue/stream.
  • Make the context object lightweight enough to pass around safely.

To facilitate this, you can still use the input variables in the context as before. Alternatively, you can use INPUT to receive a handle to a thread-safe (non context) input reader - which allows you to thread/include in threaded systems, and still access the frame’s input state. The new functions are present. If you call activate_event_queue, the back-end event queues translate their event streams into BEvent types - and you can call pop or for_each_message to handle them directly. The amount of detail you receive varies by the back-end you are using (Crossterm/Curses give a lot less detail than a native OpenGL program, for example). This allows you to intercept the user hitting “close” on the window and offer some guidance (“really quit? Save?"), pause processing when minimized, handle focus loss/restoral, and so on. I’m still working on the touch and multi-touch API.

A nice side-effect of this is that the context object is even smaller: frame rates went up across the board once again because of the reduced load (and cache inefficiency) of passing a context object around.

Bracket-Lib - Diet Context

Since I was busily giving the Context a diet, I figured I’d finish the job. The middleware portion of the library (that tracks fonts, shaders, and console content) is now detached from the context, also. It’s available if you really want it (I recommend against it - use the API!), but it no longer burdens every tick call - and you aren’t passing it down every call tree. It is now small/compact enough that it can be Cloned, supports Debug pretty printing. You still have access to the API through it: but with a couple of exceptions, you don’t need to use it to perform rendering.

If you use the command queue render system, your context just needs to submit the command queue at the end of the frame. All your systems can happily render to their own little command queues in an entirely thread-safe (and really fast) environment - with the result being composited when you tell RLTK to submit the batch. (It doesn’t auto-submit in case you want to spend more than one frame building it).

Bugfixes

  • WASM terminals no longer forget to apply your background color.
  • Calling nothing but set actually works now. I’d forgotten to update the dirty flag.
  • Minimizing and then maximizing repeatedly no longer causes a crash in the mouse code.
  • A remarkable number of small bug-fixes for small corner cases.

Rust Roguelike Tutorial | Website | Github | Twitter

A few minor bug-fixes, mostly text that needed cleaning up. There’s a backlog of fixes building up while I work on the libraries, so expect a flurry of tutorial stuff in the next week or two! (I’m in the planning/outlining stages of the next few chapters, also).

Share this post
Support the author with