41 releases

0.2.0-beta.10 Oct 30, 2024
0.2.0-beta.6 Sep 3, 2024
0.2.0-beta.5 Jul 8, 2024
0.2.0-beta.2 Mar 25, 2024
0.1.17 Oct 14, 2022

#82 in Game dev

Download history 179/week @ 2024-07-26 19/week @ 2024-08-02 85/week @ 2024-08-30 21/week @ 2024-09-06 26/week @ 2024-09-13 12/week @ 2024-09-20 10/week @ 2024-09-27 3/week @ 2024-10-04 106/week @ 2024-10-11 112/week @ 2024-10-18 199/week @ 2024-10-25 68/week @ 2024-11-01 16/week @ 2024-11-08

396 downloads per month

MIT license

90KB
2K SLoC

devotee

Simplistic visualization project.

Using devotee

Creating an app

Devotee app is represented by the Root implementation and a Backend system.

Root implementation

Root implementor must choose desired input system, pixel data converter, and render surface. Also, it has to implement update, render and converter methods.

Minimalist Root implementation may look like this:

struct Minimal;

impl Root for Minimal {
    type Input = NoInput;
    type Converter = BlackWhiteConverter;
    type RenderSurface = Canvas<bool>;

    fn update(&mut self, _: AppContext<Self::Input>) {}

    fn render(&self, _: &mut Self::RenderSurface) {}

    fn converter(&self) -> Self::Converter {
        BlackWhiteConverter
    }
}

This Root implementation:

  • Uses NoInput as input system;
  • Relies on the BlackWhiteConverter (implementation will be discussed later) to convert data of the RenderSurface;
  • Uses Canvas with bool pixels as a RenderSurface;
  • Does nothing during update;
  • Draws nothing during render;
  • Returns BlackWhiteConverter instance for data conversion;

The sample BlackWhiteConverter is implemented as:

struct BlackWhiteConverter;

impl Converter for BlackWhiteConverter {
    type Data = bool;

    fn convert(&self, _x: usize, _y: usize, data: Self::Data) -> u32 {
        if data {0xffffffff} else {0xff000000}
    }
}

It ignores x and y coordinates of the pixel and returns either pure white or pure black depending on the data value.

Backend usage

So, with the Root being implemented it is time to launch it using some backend.

For this example we will rely on the Softbuffer-based backend implementation.

fn main() -> Result<(), Error> {
    let backend = SoftBackend::try_new("minimal")?;
    backend.run(
        App::new(Minimal),
        SoftMiddleware::new(Canvas::with_resolution(false, 128, 128), NoInput),
        Duration::from_secs_f32(1.0 / 60.0),
    )
}

Updating app state

Consider Extended implementation of Root.

struct Extended {
    counter: f32,
}

Let it use Keyboard as input. It shuts down on the Escape button being pressed. Also, it counts passed simulation time in counter.

So, first part of its implementation looks like this:

impl Root for Extended {
    type Input = Keyboard;
    type Converter = BlackWhiteConverter;
    type RenderSurface = Canvas<bool>;

    fn update(&mut self, mut context: devotee::app::AppContext<Self::Input>) {
        if context.input().just_pressed(KeyCode::Escape) {
            context.shutdown();
        }

        self.counter += context.delta().as_secs_f32();
    }

    // ...
}

During render it cleans render surface, calculates the surface center and draws two filled circles using painter. Painter instance accepts functions as arguments instead of pure colors. The function decides what to do with the pixel passed given its coordinates. paint is a predefined function to override any original value.

Note that there are two implementations of painter: for i32 coordinates and (subpixel one) for f32 coordinates.

    //. ..
    fn render(&self, surface: &mut Self::RenderSurface) {
        surface.clear(false);
        let center = surface.dimensions().map(|a| a as f32) / 2.0;

        let mut painter = surface.painter();
        let radius = 48.0 + 16.0 * self.counter.sin();

        painter.circle_f(center, radius, paint(true));
        painter.circle_f(center, radius / 2.0, |x, y, _| (x + y) % 2 == 0)
    }
    // ...

Examples

There are some examples in the examples folder.

License

devotee is licensed under the MIT license.

Dependencies

~0–34MB
~540K SLoC