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}