platform/
lib.rs

1// SPDX-FileCopyrightText: 2024 Jens Pitkänen <jens.pitkanen@helsinki.fi>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5//! This crate mainly revolves around the [`Platform`] trait, which can be
6//! implemented to provide a "platform implementation" for the game engine.
7//! Otherwise, this crate mostly contains some low-level parts of the engine
8//! which are necessarily needed to implement [`Platform`], such as some
9//! multithreading utilities.
10//!
11//! This is split off of the main engine crate so that the engine and the
12//! platform implementation can be compiled independently, which appears to
13//! speed up compilation time.
14
15#![no_std]
16#![warn(missing_docs)]
17
18mod boxed;
19pub mod channel;
20mod input;
21mod io;
22mod render;
23mod semaphore;
24pub mod thread_pool;
25mod time;
26
27use arrayvec::ArrayVec;
28
29use core::fmt::Arguments;
30
31pub use boxed::*;
32pub use input::*;
33pub use io::*;
34pub use render::*;
35pub use semaphore::*;
36pub use thread_pool::{TaskChannel, ThreadState};
37pub use time::*;
38
39/// Sample rate for the audio data played back by the engine.
40pub const AUDIO_SAMPLE_RATE: u32 = 48000;
41
42/// The amount of channels of audio data played back by the engine.
43pub const AUDIO_CHANNELS: usize = 2;
44
45/// Shorthand for an [`ArrayVec`] of [`InputDevice`].
46///
47/// Exported so that platforms don't need to explicitly depend on [`arrayvec`]
48/// just for the [`Platform::input_devices`] typing.
49pub type InputDevices = ArrayVec<InputDevice, 15>;
50
51/// Interface to the engine for the platform implementation.
52///
53/// Used to allow engine to do its thing each frame, and to pass events to it.
54pub trait EngineCallbacks {
55    /// Run one frame of the game loop.
56    fn run_frame(
57        &mut self,
58        platform: &dyn Platform,
59        run_game_frame: &mut dyn FnMut(Instant, &dyn Platform, &mut Self),
60    );
61
62    /// Handle an event.
63    fn event(&mut self, event: Event, timestamp: Instant);
64}
65
66/// A trait for using platform-dependent features from the engine without
67/// depending on any platform implementation directly. A platform implementation
68/// should implement this trait, and also call the engine's "iterate" and
69/// "event" methods at appropriate times.
70///
71/// All the functions have a `&self` parameter, so that the methods can access
72/// some (possibly internally mutable) state, but still keeping the platform
73/// object as widely usable as possible (a "platform" is about as global an
74/// object as you get). Also, none of these functions are (supposed to be) hot,
75/// and this trait is object safe, so using &dyn [`Platform`] should be fine
76/// performance-wise, and will hopefully help with compilation times by avoiding
77/// generics.
78pub trait Platform {
79    /// Get the current screen size. Could be physical pixels, could be
80    /// "logical" pixels, depends on the platform, but it's the same coordinate
81    /// system as the [`Vertex2D`]es passed into [`Platform::draw_2d`].
82    fn draw_area(&self) -> (f32, f32);
83
84    /// Get the current screen scale factor. When multiplied with
85    /// [`Platform::draw_area`] should match the resolution of the framebuffer
86    /// (i.e. the resolution which sprites should match for pixel perfect
87    /// rendering).
88    fn draw_scale_factor(&self) -> f32;
89
90    /// Render out a pile of possibly textured 2D triangles.
91    fn draw_2d(&self, vertices: &[Vertex2D], indices: &[u32], settings: DrawSettings2D);
92
93    /// Create a sprite of the given size and format. Returns None if the sprite
94    /// could not be created due to any reason (sprite dimensions too large, out
95    /// of vram, etc.). See [`Vertex2D`] and [`DrawSettings2D`] for sampler
96    /// details.
97    ///
98    /// ### Implementation note
99    ///
100    /// These are never freed during the lifetime of the engine. Internally,
101    /// individual sprites are reused as they get streamed in and out.
102    fn create_sprite(&self, width: u16, height: u16, format: PixelFormat) -> Option<SpriteRef>;
103
104    /// Update the pixel data of a sprite within a region. Pixels are tightly
105    /// packed and in the same format as passed into the creation function.
106    fn update_sprite(
107        &self,
108        sprite: SpriteRef,
109        x_offset: u16,
110        y_offset: u16,
111        width: u16,
112        height: u16,
113        pixels: &[u8],
114    );
115
116    /// Open a file for reading. Returns None if the file can't be read.
117    fn open_file(&self, path: &str) -> Option<FileHandle>;
118
119    /// Start an asynchronous read operation to fill `buffer` from the `file` at
120    /// offset `first_byte`.
121    ///
122    /// Implementations can assume that `'a` will last until
123    /// [`Platform::finish_file_read`] is called with the task returned from
124    /// this function, since [`FileReadTask`] can't (safely) be dropped without
125    /// it getting called.
126    #[must_use]
127    fn begin_file_read(&self, file: FileHandle, first_byte: u64, buffer: Box<[u8]>)
128        -> FileReadTask;
129
130    /// Returns true if the read task has finished (in success or failure),
131    /// false if it's still pending.
132    fn is_file_read_finished(&self, task: &FileReadTask) -> bool;
133
134    /// Blocks until the read task finishes, and returns the buffer which the
135    /// file contents were written into, if successful. If the read fails, the
136    /// memory is returned wrapped in an `Err`, and the buffer contents are not
137    /// guaranteed.
138    fn finish_file_read(&self, task: FileReadTask) -> Result<Box<[u8]>, Box<[u8]>>;
139
140    /// Creates a semaphore.
141    ///
142    /// Multi-threaded platforms should use [`Semaphore::new`] and implement the
143    /// functions so that they make use of OS synchronization primitives.
144    /// Single-threaded platforms can use [`Semaphore::single_threaded`].
145    fn create_semaphore(&self) -> Semaphore;
146
147    /// Returns how many threads the system could process in parallel
148    /// efficiently.
149    ///
150    /// Note that this count shouldn't be decremented by one to "leave room for
151    /// the main thread," because the main thread often sleeps while waiting for
152    /// worker threads to finish their work.
153    ///
154    /// If this returns 1, the thread pool will not utilize worker threads, and
155    /// `spawn_pool_thread` can be left `unimplemented!`.
156    fn available_parallelism(&self) -> usize;
157
158    /// Spawns a thread for a thread pool, using the given channels to pass
159    /// tasks back and forth.
160    ///
161    /// Implementation note: unless the build has `panic = "abort"`, the worker
162    /// thread should catch panics, and if they happen, call `signal_panic` on
163    /// the task and send the task back to the main thread via the results
164    /// channel, and *then* resume the panic. This will avoid the main thread
165    /// silently hanging when joining tasks that panicked the thread they were
166    /// running on, it'll panic instead, with a message about a thread pool
167    /// thread panicking.
168    fn spawn_pool_thread(&self, channels: [TaskChannel; 2]) -> ThreadState;
169
170    /// Passes a buffer of audio samples to be played back, and the playback
171    /// position where the samples start.
172    ///
173    /// Each sample should be a tuple containing the left and right channels'
174    /// audio samples for stereo playback, in that order.
175    ///
176    /// The playback position where the platform will start reading can be
177    /// queried with [`Platform::audio_playback_position`].
178    fn update_audio_buffer(&self, first_position: u64, samples: &[[i16; AUDIO_CHANNELS]]);
179
180    /// Returns the playback position of the next sample the platform will play,
181    /// and the timestamp which it should be considered to be synchronized with.
182    ///
183    /// Any samples submitted with [`Platform::update_audio_buffer`] before this
184    /// position will be ignored.
185    fn audio_playback_position(&self) -> (u64, Instant);
186
187    /// Get a list of the currently connected input devices.
188    fn input_devices(&self) -> InputDevices;
189
190    /// Get the default button for one of the generic action categories for the
191    /// given input device, if a default exists.
192    fn default_button_for_action(
193        &self,
194        action: ActionCategory,
195        device: InputDevice,
196    ) -> Option<Button>;
197
198    /// Returns the current point in time according to the platform
199    /// implementation.
200    fn now(&self) -> Instant;
201
202    /// Print out a string. For very crude debugging.
203    fn println(&self, message: Arguments);
204
205    /// Request the process to exit, with `clean: false` if intending to signal
206    /// failure. On a clean exit, the exit may be delayed until a moment later,
207    /// e.g. at the end of the current frame of the game loop, and after
208    /// resource clean up. In failure cases, the idea is to bail asap, but it's
209    /// up to the platform.
210    fn exit(&self, clean: bool);
211}