move video, audio and input to new SystemResources trait/structs
System now uses a generic SystemResources structure to provide video, audio and input devices the DosLike implementation of SystemResources provides the same functionality that this library has had to date. this change is to prepare for future addition of things like 32-bit Bitmap support and other possible audio device implementations, etc. the idea is a custom SystemResources implementation can be substituted in to configure everything with the desired functionality.
This commit is contained in:
parent
43b2c9df7c
commit
f1c04d85e3
|
@ -40,7 +40,7 @@ impl SineWaveGenerator {
|
|||
}
|
||||
|
||||
impl AudioGenerator for SineWaveGenerator {
|
||||
fn gen_sample(&mut self, position: usize) -> Option<u8> {
|
||||
fn gen_sample(&mut self, _position: usize) -> Option<u8> {
|
||||
const MAX_TIME: usize = AUDIO_FREQUENCY_22KHZ as usize * 3; // 3 seconds
|
||||
if self.t < MAX_TIME {
|
||||
let sample = (self.t as f64 * 0.25).sin() * 80.0;
|
||||
|
@ -53,103 +53,104 @@ impl AudioGenerator for SineWaveGenerator {
|
|||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut system = SystemBuilder::new().window_title("Audio Playback").vsync(true).build()?;
|
||||
let config = DosLikeConfig::new().vsync(true);
|
||||
let mut system = SystemBuilder::new().window_title("Audio Playback").build(config)?;
|
||||
|
||||
let mut using_queue_commands = false;
|
||||
let mut volume = 1.0;
|
||||
|
||||
let sounds = [
|
||||
load_and_convert_wav(Path::new("./assets/pickup-coin.wav"), system.audio.spec())?,
|
||||
load_and_convert_wav(Path::new("./assets/powerup.wav"), system.audio.spec())?,
|
||||
load_and_convert_wav(Path::new("./assets/explosion.wav"), system.audio.spec())?,
|
||||
load_and_convert_wav(Path::new("./assets/jump.wav"), system.audio.spec())?,
|
||||
load_and_convert_wav(Path::new("./assets/laser-shoot.wav"), system.audio.spec())?,
|
||||
load_and_convert_wav(Path::new("./assets/pickup-coin.wav"), system.res.audio.spec())?,
|
||||
load_and_convert_wav(Path::new("./assets/powerup.wav"), system.res.audio.spec())?,
|
||||
load_and_convert_wav(Path::new("./assets/explosion.wav"), system.res.audio.spec())?,
|
||||
load_and_convert_wav(Path::new("./assets/jump.wav"), system.res.audio.spec())?,
|
||||
load_and_convert_wav(Path::new("./assets/laser-shoot.wav"), system.res.audio.spec())?,
|
||||
];
|
||||
|
||||
let mut statuses = [AudioChannelStatus { size: 0, position: 0, playing: false }; NUM_CHANNELS];
|
||||
|
||||
while !system.do_events() {
|
||||
if system.input_devices.keyboard.is_key_pressed(Scancode::Escape) {
|
||||
while !system.do_events()? {
|
||||
if system.res.keyboard.is_key_pressed(Scancode::Escape) {
|
||||
break;
|
||||
}
|
||||
|
||||
let mut audio_device = system.audio.lock();
|
||||
let mut audio_device = system.res.audio.lock();
|
||||
audio_device.volume = volume;
|
||||
|
||||
if system.input_devices.keyboard.is_key_pressed(Scancode::Num1) {
|
||||
if system.res.keyboard.is_key_pressed(Scancode::Num1) {
|
||||
if using_queue_commands {
|
||||
system.audio_queue.play_buffer(&sounds[0], false);
|
||||
system.res.audio_queue.play_buffer(&sounds[0], false)?;
|
||||
} else {
|
||||
audio_device.play_buffer(&sounds[0], false)?;
|
||||
}
|
||||
}
|
||||
|
||||
if system.input_devices.keyboard.is_key_pressed(Scancode::Num2) {
|
||||
if system.res.keyboard.is_key_pressed(Scancode::Num2) {
|
||||
if using_queue_commands {
|
||||
system.audio_queue.play_buffer(&sounds[1], false);
|
||||
system.res.audio_queue.play_buffer(&sounds[1], false)?;
|
||||
} else {
|
||||
audio_device.play_buffer(&sounds[1], false)?;
|
||||
}
|
||||
}
|
||||
|
||||
if system.input_devices.keyboard.is_key_pressed(Scancode::Num3) {
|
||||
if system.res.keyboard.is_key_pressed(Scancode::Num3) {
|
||||
if using_queue_commands {
|
||||
system.audio_queue.play_buffer(&sounds[2], false);
|
||||
system.res.audio_queue.play_buffer(&sounds[2], false)?;
|
||||
} else {
|
||||
audio_device.play_buffer(&sounds[2], false)?;
|
||||
}
|
||||
}
|
||||
|
||||
if system.input_devices.keyboard.is_key_pressed(Scancode::Num4) {
|
||||
if system.res.keyboard.is_key_pressed(Scancode::Num4) {
|
||||
if using_queue_commands {
|
||||
system.audio_queue.play_buffer(&sounds[3], false);
|
||||
system.res.audio_queue.play_buffer(&sounds[3], false)?;
|
||||
} else {
|
||||
audio_device.play_buffer(&sounds[3], false)?;
|
||||
}
|
||||
}
|
||||
|
||||
if system.input_devices.keyboard.is_key_pressed(Scancode::Num5) {
|
||||
if system.res.keyboard.is_key_pressed(Scancode::Num5) {
|
||||
if using_queue_commands {
|
||||
system.audio_queue.play_buffer(&sounds[4], false);
|
||||
system.res.audio_queue.play_buffer(&sounds[4], false)?;
|
||||
} else {
|
||||
audio_device.play_buffer(&sounds[4], false)?;
|
||||
}
|
||||
}
|
||||
|
||||
if system.input_devices.keyboard.is_key_pressed(Scancode::Num6) {
|
||||
if system.res.keyboard.is_key_pressed(Scancode::Num6) {
|
||||
if using_queue_commands {
|
||||
system.audio_queue.play_generator(Box::new(SineWaveGenerator::new()), false);
|
||||
system.res.audio_queue.play_generator(Box::new(SineWaveGenerator::new()), false)?;
|
||||
} else {
|
||||
audio_device.play_generator(Box::new(SineWaveGenerator::new()), false);
|
||||
audio_device.play_generator(Box::new(SineWaveGenerator::new()), false)?;
|
||||
}
|
||||
}
|
||||
|
||||
if system.input_devices.keyboard.is_key_pressed(Scancode::Num7) {
|
||||
if system.res.keyboard.is_key_pressed(Scancode::Num7) {
|
||||
let index = rnd_value(0, sounds.len() - 1);
|
||||
if using_queue_commands {
|
||||
system.audio_queue.play_buffer_on_channel(7, &sounds[index], false)?;
|
||||
system.res.audio_queue.play_buffer_on_channel(7, &sounds[index], false)?;
|
||||
} else {
|
||||
audio_device.play_buffer_on_channel(7, &sounds[index], false)?;
|
||||
}
|
||||
}
|
||||
|
||||
if system.input_devices.keyboard.is_key_pressed(Scancode::S) {
|
||||
if system.res.keyboard.is_key_pressed(Scancode::S) {
|
||||
if using_queue_commands {
|
||||
system.audio_queue.stop_all();
|
||||
system.res.audio_queue.stop_all();
|
||||
} else {
|
||||
audio_device.stop_all();
|
||||
}
|
||||
}
|
||||
|
||||
system.audio_queue.apply_to_device(&mut audio_device)?;
|
||||
system.res.audio_queue.apply_to_device(&mut audio_device)?;
|
||||
|
||||
if system.input_devices.keyboard.is_key_pressed(Scancode::KpMinus) {
|
||||
if system.res.keyboard.is_key_pressed(Scancode::KpMinus) {
|
||||
volume -= 0.1;
|
||||
}
|
||||
if system.input_devices.keyboard.is_key_pressed(Scancode::KpPlus) {
|
||||
if system.res.keyboard.is_key_pressed(Scancode::KpPlus) {
|
||||
volume += 0.1;
|
||||
}
|
||||
if system.input_devices.keyboard.is_key_pressed(Scancode::Q) {
|
||||
if system.res.keyboard.is_key_pressed(Scancode::Q) {
|
||||
using_queue_commands = !using_queue_commands;
|
||||
}
|
||||
|
||||
|
@ -163,24 +164,31 @@ fn main() -> Result<()> {
|
|||
|
||||
drop(audio_device);
|
||||
|
||||
system.video.clear(0);
|
||||
system.update()?;
|
||||
system.res.video.clear(0);
|
||||
|
||||
system.video.print_string(&format!("Volume: {:2.2}", volume), 16, 16, FontRenderOpts::Color(10), &system.font);
|
||||
system.video.print_string(
|
||||
system.res.video.print_string(
|
||||
&format!("Volume: {:2.2}", volume),
|
||||
16, 16, FontRenderOpts::Color(10), &system.res.font
|
||||
);
|
||||
system.res.video.print_string(
|
||||
if using_queue_commands {
|
||||
"Queueing Commands"
|
||||
} else {
|
||||
"Direct Commands"
|
||||
},
|
||||
160, 16, FontRenderOpts::Color(9), &system.font,
|
||||
160, 16, FontRenderOpts::Color(9), &system.res.font,
|
||||
);
|
||||
|
||||
system.video.print_string("Audio Channels", 16, 32, FontRenderOpts::Color(14), &system.font);
|
||||
system.res.video.print_string(
|
||||
"Audio Channels",
|
||||
16, 32, FontRenderOpts::Color(14), &system.res.font
|
||||
);
|
||||
|
||||
let mut y = 48;
|
||||
for index in 0..NUM_CHANNELS {
|
||||
let status = &statuses[index];
|
||||
system.video.print_string(
|
||||
system.res.video.print_string(
|
||||
&format!(
|
||||
"channel {} - {} {}",
|
||||
index,
|
||||
|
@ -189,7 +197,7 @@ fn main() -> Result<()> {
|
|||
),
|
||||
16, y,
|
||||
FontRenderOpts::Color(15),
|
||||
&system.font,
|
||||
&system.res.font,
|
||||
);
|
||||
y += 16;
|
||||
}
|
||||
|
|
|
@ -22,15 +22,15 @@ struct Ball {
|
|||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let config = DosLikeConfig::new().vsync(true);
|
||||
let mut system = SystemBuilder::new()
|
||||
.window_title("Flying Balls!")
|
||||
.vsync(true)
|
||||
.build()?;
|
||||
.build(config)?;
|
||||
|
||||
let font = BitmaskFont::new_vga_font()?;
|
||||
|
||||
let (balls_bmp, balls_palette) = Bitmap::load_pcx_file(Path::new("./assets/balls.pcx"))?;
|
||||
system.palette = balls_palette.clone();
|
||||
system.res.palette = balls_palette.clone();
|
||||
|
||||
let mut sprites = Vec::<Bitmap>::new();
|
||||
let mut balls = Vec::<Ball>::new();
|
||||
|
@ -59,12 +59,12 @@ fn main() -> Result<()> {
|
|||
balls.push(ball);
|
||||
}
|
||||
|
||||
while !system.do_events() {
|
||||
if system.input_devices.keyboard.is_key_pressed(Scancode::Escape) {
|
||||
while !system.do_events()? {
|
||||
if system.res.keyboard.is_key_pressed(Scancode::Escape) {
|
||||
break;
|
||||
}
|
||||
|
||||
if system.input_devices.keyboard.is_key_up(Scancode::S) {
|
||||
if system.res.keyboard.is_key_up(Scancode::S) {
|
||||
for i in 0..NUM_BALLS {
|
||||
let ball = &mut balls[i];
|
||||
ball.x += ball.dir_x;
|
||||
|
@ -96,14 +96,13 @@ fn main() -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
system.video.clear(2);
|
||||
system.update()?;
|
||||
system.res.video.clear(2);
|
||||
|
||||
system
|
||||
.video
|
||||
.print_string("hello, world!", 10, 10, FontRenderOpts::Color(15), &font);
|
||||
system.res.video.print_string("hello, world!", 10, 10, FontRenderOpts::Color(15), &font);
|
||||
|
||||
for i in 0..NUM_BALLS {
|
||||
system.video.blit(
|
||||
system.res.video.blit(
|
||||
BlitMethod::Transparent(0),
|
||||
&sprites[balls[i].sprite],
|
||||
balls[i].x,
|
||||
|
|
|
@ -165,7 +165,7 @@ fn render_system_sprites(context: &mut Context) {
|
|||
|
||||
for (entity, sprite_index) in sprite_indices.iter() {
|
||||
let position = positions.get(&entity).unwrap();
|
||||
context.system.video.blit(
|
||||
context.system.res.video.blit(
|
||||
BlitMethod::Transparent(0),
|
||||
&context.sprites[sprite_index.0],
|
||||
position.0.x as i32,
|
||||
|
@ -206,7 +206,7 @@ fn render_system_particles(context: &mut Context) {
|
|||
}
|
||||
|
||||
if let Some(color) = pixel_color {
|
||||
context.system.video.set_pixel(position.0.x as i32, position.0.y as i32, color);
|
||||
context.system.res.video.set_pixel(position.0.x as i32, position.0.y as i32, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,8 @@ mod entities;
|
|||
mod states;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let system = SystemBuilder::new().window_title("Flying Balls").vsync(true).build()?;
|
||||
let config = DosLikeConfig::new().vsync(true);
|
||||
let system = SystemBuilder::new().window_title("Flying Balls").build(config)?;
|
||||
let mut game = Game::new(system)?;
|
||||
let mut states = States::new();
|
||||
states.push(SimulationState)?;
|
||||
|
@ -20,15 +21,16 @@ fn main() -> Result<()> {
|
|||
let tick_frequency = game.context.system.tick_frequency();
|
||||
let mut last_ticks = game.context.system.ticks();
|
||||
|
||||
while !game.context.system.do_events() && !states.is_empty() {
|
||||
while !game.context.system.do_events()? && !states.is_empty() {
|
||||
let ticks = game.context.system.ticks();
|
||||
let elapsed = ticks - last_ticks;
|
||||
last_ticks = ticks;
|
||||
game.context.delta = (elapsed as f64 / tick_frequency as f64) as f32;
|
||||
|
||||
states.update(&mut game)?;
|
||||
game.context.system.update()?;
|
||||
|
||||
game.context.system.video.clear(0);
|
||||
game.context.system.res.video.clear(0);
|
||||
states.render(&mut game);
|
||||
|
||||
game.context.system.display()?;
|
||||
|
|
|
@ -13,7 +13,7 @@ pub const NUM_BALL_SPRITES: usize = 16;
|
|||
|
||||
pub struct Context {
|
||||
pub delta: f32,
|
||||
pub system: System,
|
||||
pub system: System<DosLike>,
|
||||
pub font: BitmaskFont,
|
||||
pub sprites: Vec<Bitmap>,
|
||||
pub entities: Entities,
|
||||
|
@ -27,11 +27,11 @@ pub struct Game {
|
|||
}
|
||||
|
||||
impl Game {
|
||||
pub fn new(mut system: System) -> Result<Self> {
|
||||
pub fn new(mut system: System<DosLike>) -> Result<Self> {
|
||||
let font = BitmaskFont::new_vga_font()?;
|
||||
|
||||
let (balls_bmp, balls_palette) = Bitmap::load_pcx_file(Path::new("./assets/balls.pcx"))?;
|
||||
system.palette = balls_palette.clone();
|
||||
system.res.palette = balls_palette.clone();
|
||||
|
||||
let mut sprites = Vec::new();
|
||||
for i in 0..NUM_BALL_SPRITES {
|
||||
|
@ -78,12 +78,12 @@ pub struct SimulationState;
|
|||
|
||||
impl AppState<Game> for SimulationState {
|
||||
fn update(&mut self, _state: State, context: &mut Game) -> Option<StateChange<Game>> {
|
||||
if context.context.system.input_devices.keyboard.is_key_up(Scancode::S) {
|
||||
if context.context.system.res.keyboard.is_key_up(Scancode::S) {
|
||||
context.do_events();
|
||||
context.component_systems.update(&mut context.context);
|
||||
}
|
||||
|
||||
if context.context.system.input_devices.keyboard.is_key_pressed(Scancode::Escape) {
|
||||
if context.context.system.res.keyboard.is_key_pressed(Scancode::Escape) {
|
||||
return Some(StateChange::Pop(1));
|
||||
}
|
||||
|
||||
|
@ -91,9 +91,9 @@ impl AppState<Game> for SimulationState {
|
|||
}
|
||||
|
||||
fn render(&mut self, _state: State, context: &mut Game) {
|
||||
context.context.system.video.clear(2);
|
||||
context.context.system.res.video.clear(2);
|
||||
context.component_systems.render(&mut context.context);
|
||||
context.context.system.video.print_string("hello, world!", 10, 10, FontRenderOpts::Color(15), &context.context.font);
|
||||
context.context.system.res.video.print_string("hello, world!", 10, 10, FontRenderOpts::Color(15), &context.context.font);
|
||||
}
|
||||
|
||||
fn transition(&mut self, _state: State, _context: &mut Game) -> bool {
|
||||
|
|
|
@ -692,7 +692,7 @@ fn render_system_sprites(context: &mut Core) {
|
|||
// now render them in the correct order ...
|
||||
for (entity, position, blit_method) in context.sprite_render_list.iter() {
|
||||
let sprite = sprites.get(entity).unwrap();
|
||||
context.system.video.blit_atlas(
|
||||
context.system.res.video.blit_atlas(
|
||||
blit_method.clone(),
|
||||
&sprite.atlas,
|
||||
sprite.index,
|
||||
|
@ -710,7 +710,7 @@ fn render_system_pixels(context: &mut Core) {
|
|||
if let Some((_, camera)) = context.entities.components::<Camera>().single() {
|
||||
for (entity, pixel) in pixels.iter() {
|
||||
if let Some(position) = positions.get(entity) {
|
||||
context.system.video.set_pixel(
|
||||
context.system.res.video.set_pixel(
|
||||
position.0.x as i32 - camera.x,
|
||||
position.0.y as i32 - camera.y,
|
||||
pixel.0,
|
||||
|
|
|
@ -28,7 +28,7 @@ pub const TILE_HEIGHT: u32 = 16;
|
|||
|
||||
pub struct Core {
|
||||
pub delta: f32,
|
||||
pub system: System,
|
||||
pub system: System<DosLike>,
|
||||
pub font: BitmaskFont,
|
||||
pub entities: Entities,
|
||||
pub event_publisher: EventPublisher<Event>,
|
||||
|
@ -54,12 +54,12 @@ pub struct Core {
|
|||
pub sprite_render_list: Vec<(EntityId, Vector2, BlitMethod)>,
|
||||
}
|
||||
|
||||
impl CoreState for Core {
|
||||
fn system(&self) -> &System {
|
||||
impl CoreState<DosLike> for Core {
|
||||
fn system(&self) -> &System<DosLike> {
|
||||
&self.system
|
||||
}
|
||||
|
||||
fn system_mut(&mut self) -> &mut System {
|
||||
fn system_mut(&mut self) -> &mut System<DosLike> {
|
||||
&mut self.system
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,7 @@ impl CoreState for Core {
|
|||
}
|
||||
}
|
||||
|
||||
impl CoreStateWithEvents<Event> for Core {
|
||||
impl CoreStateWithEvents<DosLike, Event> for Core {
|
||||
fn event_publisher(&mut self) -> &mut EventPublisher<Event> {
|
||||
&mut self.event_publisher
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ pub struct Support {
|
|||
|
||||
impl SupportSystems for Support {}
|
||||
|
||||
impl SupportSystemsWithEvents<Event> for Support {
|
||||
impl SupportSystemsWithEvents<DosLike, Event> for Support {
|
||||
type ContextType = Core;
|
||||
|
||||
fn event_listeners(&mut self) -> &mut EventListeners<Event, Self::ContextType> {
|
||||
|
@ -98,7 +98,7 @@ pub struct Game {
|
|||
pub support: Support,
|
||||
}
|
||||
|
||||
impl AppContext for Game {
|
||||
impl AppContext<DosLike> for Game {
|
||||
type CoreType = Core;
|
||||
type SupportType = Support;
|
||||
|
||||
|
@ -112,9 +112,9 @@ impl AppContext for Game {
|
|||
}
|
||||
|
||||
impl Game {
|
||||
pub fn new(mut system: System) -> Result<Self> {
|
||||
pub fn new(mut system: System<DosLike>) -> Result<Self> {
|
||||
let palette = load_palette(Path::new("./assets/db16.pal"))?;
|
||||
system.palette = palette.clone();
|
||||
system.res.palette = palette.clone();
|
||||
|
||||
let font = load_font(Path::new("./assets/dp.fnt"))?;
|
||||
|
||||
|
@ -196,7 +196,8 @@ impl Game {
|
|||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let system = SystemBuilder::new().window_title("Slime Stabbing Simulator").vsync(true).build()?;
|
||||
let config = DosLikeConfig::new().vsync(true);
|
||||
let system = SystemBuilder::new().window_title("Slime Stabbing Simulator").build(config)?;
|
||||
let game = Game::new(system)?;
|
||||
main_loop(game, MainMenuState::new()).context("Main loop error")
|
||||
}
|
||||
|
|
|
@ -27,17 +27,17 @@ impl MainMenuState {
|
|||
impl AppState<Game> for MainMenuState {
|
||||
fn update(&mut self, state: State, context: &mut Game) -> Option<StateChange<Game>> {
|
||||
if state == State::Active {
|
||||
if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Escape) {
|
||||
if context.core.system.res.keyboard.is_key_pressed(Scancode::Escape) {
|
||||
return Some(StateChange::Pop(1));
|
||||
}
|
||||
if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Up) {
|
||||
if context.core.system.res.keyboard.is_key_pressed(Scancode::Up) {
|
||||
self.selection = (self.selection - 1).clamp(0, 1);
|
||||
}
|
||||
if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Down) {
|
||||
if context.core.system.res.keyboard.is_key_pressed(Scancode::Down) {
|
||||
self.selection = (self.selection + 1).clamp(0, 1);
|
||||
}
|
||||
|
||||
if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Return) {
|
||||
if context.core.system.res.keyboard.is_key_pressed(Scancode::Return) {
|
||||
match self.selection {
|
||||
0 => return Some(StateChange::Push(Box::new(GamePlayState::new()))),
|
||||
1 => return Some(StateChange::Pop(1)),
|
||||
|
@ -53,7 +53,7 @@ impl AppState<Game> for MainMenuState {
|
|||
}
|
||||
|
||||
fn render(&mut self, state: State, context: &mut Game) {
|
||||
context.core.tilemap.draw(&mut context.core.system.video, &context.core.tiles, 0, 0);
|
||||
context.core.tilemap.draw(&mut context.core.system.res.video, &context.core.tiles, 0, 0);
|
||||
context.support.component_systems.render(&mut context.core);
|
||||
|
||||
let x = 32;
|
||||
|
@ -61,13 +61,13 @@ impl AppState<Game> for MainMenuState {
|
|||
let width = 48;
|
||||
let height = 40;
|
||||
const SPACER: i32 = 8;
|
||||
draw_window(&mut context.core.system.video, &context.core.ui, x, y, x + width, y + height);
|
||||
draw_window(&mut context.core.system.res.video, &context.core.ui, x, y, x + width, y + height);
|
||||
|
||||
let selection_y = y + SPACER + (self.selection as i32 * 16);
|
||||
context.core.system.video.print_string(">", x + SPACER, selection_y, FontRenderOpts::Color(15), &context.core.font);
|
||||
context.core.system.res.video.print_string(">", x + SPACER, selection_y, FontRenderOpts::Color(15), &context.core.font);
|
||||
|
||||
context.core.system.video.print_string("Play", x + SPACER + SPACER, y + SPACER, FontRenderOpts::Color(15), &context.core.font);
|
||||
context.core.system.video.print_string("Quit", x + SPACER + SPACER, y + SPACER + 16, FontRenderOpts::Color(15), &context.core.font);
|
||||
context.core.system.res.video.print_string("Play", x + SPACER + SPACER, y + SPACER, FontRenderOpts::Color(15), &context.core.font);
|
||||
context.core.system.res.video.print_string("Quit", x + SPACER + SPACER, y + SPACER + 16, FontRenderOpts::Color(15), &context.core.font);
|
||||
}
|
||||
|
||||
fn transition(&mut self, state: State, context: &mut Game) -> bool {
|
||||
|
@ -86,7 +86,7 @@ impl AppState<Game> for MainMenuState {
|
|||
self.fade = 1.0;
|
||||
}
|
||||
State::Paused => {
|
||||
context.core.system.palette = context.core.palette.clone();
|
||||
context.core.system.res.palette = context.core.palette.clone();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
@ -113,17 +113,17 @@ impl AppState<Game> for GamePlayState {
|
|||
fn update(&mut self, state: State, context: &mut Game) -> Option<StateChange<Game>> {
|
||||
if state == State::Active {
|
||||
if self.in_menu {
|
||||
if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Escape) {
|
||||
if context.core.system.res.keyboard.is_key_pressed(Scancode::Escape) {
|
||||
self.in_menu = false;
|
||||
}
|
||||
if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Up) {
|
||||
if context.core.system.res.keyboard.is_key_pressed(Scancode::Up) {
|
||||
self.selection = (self.selection - 1).clamp(0, 1);
|
||||
}
|
||||
if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Down) {
|
||||
if context.core.system.res.keyboard.is_key_pressed(Scancode::Down) {
|
||||
self.selection = (self.selection + 1).clamp(0, 1);
|
||||
}
|
||||
|
||||
if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Return) {
|
||||
if context.core.system.res.keyboard.is_key_pressed(Scancode::Return) {
|
||||
match self.selection {
|
||||
0 => self.in_menu = false,
|
||||
1 => return Some(StateChange::Pop(1)),
|
||||
|
@ -131,24 +131,24 @@ impl AppState<Game> for GamePlayState {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Escape) {
|
||||
if context.core.system.res.keyboard.is_key_pressed(Scancode::Escape) {
|
||||
self.in_menu = true;
|
||||
}
|
||||
|
||||
if let Some((player_entity, _)) = context.core.entities.components::<Player>().single() {
|
||||
if context.core.system.input_devices.keyboard.is_key_down(Scancode::Up) {
|
||||
if context.core.system.res.keyboard.is_key_down(Scancode::Up) {
|
||||
context.core.event_publisher.queue(Event::TurnAndMove(*player_entity, Direction::North));
|
||||
}
|
||||
if context.core.system.input_devices.keyboard.is_key_down(Scancode::Down) {
|
||||
if context.core.system.res.keyboard.is_key_down(Scancode::Down) {
|
||||
context.core.event_publisher.queue(Event::TurnAndMove(*player_entity, Direction::South));
|
||||
}
|
||||
if context.core.system.input_devices.keyboard.is_key_down(Scancode::Left) {
|
||||
if context.core.system.res.keyboard.is_key_down(Scancode::Left) {
|
||||
context.core.event_publisher.queue(Event::TurnAndMove(*player_entity, Direction::West));
|
||||
}
|
||||
if context.core.system.input_devices.keyboard.is_key_down(Scancode::Right) {
|
||||
if context.core.system.res.keyboard.is_key_down(Scancode::Right) {
|
||||
context.core.event_publisher.queue(Event::TurnAndMove(*player_entity, Direction::East));
|
||||
}
|
||||
if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Space) {
|
||||
if context.core.system.res.keyboard.is_key_pressed(Scancode::Space) {
|
||||
context.core.event_publisher.queue(Event::Attack(*player_entity));
|
||||
}
|
||||
}
|
||||
|
@ -163,7 +163,7 @@ impl AppState<Game> for GamePlayState {
|
|||
|
||||
fn render(&mut self, state: State, context: &mut Game) {
|
||||
if let Some((_, camera)) = context.core.entities.components::<Camera>().single() {
|
||||
context.core.tilemap.draw(&mut context.core.system.video, &context.core.tiles, camera.x, camera.y);
|
||||
context.core.tilemap.draw(&mut context.core.system.res.video, &context.core.tiles, camera.x, camera.y);
|
||||
}
|
||||
context.support.component_systems.render(&mut context.core);
|
||||
|
||||
|
@ -173,13 +173,13 @@ impl AppState<Game> for GamePlayState {
|
|||
let width = 80;
|
||||
let height = 40;
|
||||
const SPACER: i32 = 8;
|
||||
draw_window(&mut context.core.system.video, &context.core.ui, x, y, x + width, y + height);
|
||||
draw_window(&mut context.core.system.res.video, &context.core.ui, x, y, x + width, y + height);
|
||||
|
||||
let selection_y = y + SPACER + (self.selection as i32 * 16);
|
||||
context.core.system.video.print_string(">", x + SPACER, selection_y, FontRenderOpts::Color(15), &context.core.font);
|
||||
context.core.system.res.video.print_string(">", x + SPACER, selection_y, FontRenderOpts::Color(15), &context.core.font);
|
||||
|
||||
context.core.system.video.print_string("Continue", x + SPACER + SPACER, y + SPACER, FontRenderOpts::Color(15), &context.core.font);
|
||||
context.core.system.video.print_string("Quit", x + SPACER + SPACER, y + SPACER + 16, FontRenderOpts::Color(15), &context.core.font);
|
||||
context.core.system.res.video.print_string("Continue", x + SPACER + SPACER, y + SPACER, FontRenderOpts::Color(15), &context.core.font);
|
||||
context.core.system.res.video.print_string("Quit", x + SPACER + SPACER, y + SPACER + 16, FontRenderOpts::Color(15), &context.core.font);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -58,10 +58,10 @@ pub fn update_fade_transition(state: State, fade: &mut f32, delta: f32, context:
|
|||
*fade += delta;
|
||||
if *fade >= 1.0 {
|
||||
*fade = 1.0;
|
||||
context.core.system.palette = context.core.palette.clone();
|
||||
context.core.system.res.palette = context.core.palette.clone();
|
||||
true
|
||||
} else {
|
||||
context.core.system.palette.lerp(0..=255, &context.core.fade_out_palette, &context.core.palette, *fade);
|
||||
context.core.system.res.palette.lerp(0..=255, &context.core.fade_out_palette, &context.core.palette, *fade);
|
||||
false
|
||||
}
|
||||
}
|
||||
|
@ -69,10 +69,10 @@ pub fn update_fade_transition(state: State, fade: &mut f32, delta: f32, context:
|
|||
*fade -= delta;
|
||||
if *fade <= 0.0 {
|
||||
*fade = 0.0;
|
||||
context.core.system.palette = context.core.fade_out_palette.clone();
|
||||
context.core.system.res.palette = context.core.fade_out_palette.clone();
|
||||
true
|
||||
} else {
|
||||
context.core.system.palette.lerp(0..=255, &context.core.fade_out_palette, &context.core.palette, *fade);
|
||||
context.core.system.res.palette.lerp(0..=255, &context.core.fade_out_palette, &context.core.palette, *fade);
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ pub fn update_system_movement(context: &mut Core) {
|
|||
pub fn update_system_remove_offscreen(context: &mut Core) {
|
||||
let positions = context.entities.components::<Position>().unwrap();
|
||||
for (entity, position) in positions.iter() {
|
||||
if !context.system.video.is_xy_visible(position.0.x as i32, position.0.y as i32) {
|
||||
if !context.system.res.video.is_xy_visible(position.0.x as i32, position.0.y as i32) {
|
||||
context.event_publisher.queue(Event::Remove(*entity));
|
||||
}
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ pub fn render_system_pixels(context: &mut Core) {
|
|||
|
||||
for (entity, position) in positions.iter() {
|
||||
let color = colors.get(entity).unwrap();
|
||||
context.system.video.set_pixel(position.0.x as i32, position.0.y as i32, color.0);
|
||||
context.system.res.video.set_pixel(position.0.x as i32, position.0.y as i32, color.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,7 +100,7 @@ impl DemoState {
|
|||
impl AppState<App> for DemoState {
|
||||
fn update(&mut self, state: State, context: &mut App) -> Option<StateChange<App>> {
|
||||
if state == State::Active {
|
||||
if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Escape) {
|
||||
if context.core.system.res.keyboard.is_key_pressed(Scancode::Escape) {
|
||||
return Some(StateChange::Pop(1));
|
||||
}
|
||||
}
|
||||
|
@ -116,7 +116,7 @@ impl AppState<App> for DemoState {
|
|||
}
|
||||
|
||||
fn render(&mut self, state: State, context: &mut App) {
|
||||
context.core.system.video.clear(0);
|
||||
context.core.system.res.video.clear(0);
|
||||
context.support.component_systems.render(&mut context.core);
|
||||
}
|
||||
|
||||
|
@ -138,17 +138,17 @@ impl AppState<App> for DemoState {
|
|||
|
||||
pub struct Core {
|
||||
pub delta: f32,
|
||||
pub system: System,
|
||||
pub system: System<DosLike>,
|
||||
pub entities: Entities,
|
||||
pub event_publisher: EventPublisher<Event>,
|
||||
}
|
||||
|
||||
impl CoreState for Core {
|
||||
fn system(&self) -> &System {
|
||||
impl CoreState<DosLike> for Core {
|
||||
fn system(&self) -> &System<DosLike> {
|
||||
&self.system
|
||||
}
|
||||
|
||||
fn system_mut(&mut self) -> &mut System {
|
||||
fn system_mut(&mut self) -> &mut System<DosLike> {
|
||||
&mut self.system
|
||||
}
|
||||
|
||||
|
@ -161,7 +161,7 @@ impl CoreState for Core {
|
|||
}
|
||||
}
|
||||
|
||||
impl CoreStateWithEvents<Event> for Core {
|
||||
impl CoreStateWithEvents<DosLike, Event> for Core {
|
||||
fn event_publisher(&mut self) -> &mut EventPublisher<Event> {
|
||||
&mut self.event_publisher
|
||||
}
|
||||
|
@ -174,7 +174,7 @@ pub struct Support {
|
|||
|
||||
impl SupportSystems for Support {}
|
||||
|
||||
impl SupportSystemsWithEvents<Event> for Support {
|
||||
impl SupportSystemsWithEvents<DosLike, Event> for Support {
|
||||
type ContextType = Core;
|
||||
|
||||
fn event_listeners(&mut self) -> &mut EventListeners<Event, Self::ContextType> {
|
||||
|
@ -187,7 +187,7 @@ pub struct App {
|
|||
pub support: Support,
|
||||
}
|
||||
|
||||
impl AppContext for App {
|
||||
impl AppContext<DosLike> for App {
|
||||
type CoreType = Core;
|
||||
type SupportType = Support;
|
||||
|
||||
|
@ -201,7 +201,7 @@ impl AppContext for App {
|
|||
}
|
||||
|
||||
impl App {
|
||||
pub fn new(system: System) -> Result<Self> {
|
||||
pub fn new(system: System<DosLike>) -> Result<Self> {
|
||||
let entities = Entities::new();
|
||||
let component_systems = ComponentSystems::new();
|
||||
let event_publisher = EventPublisher::new();
|
||||
|
@ -225,7 +225,8 @@ impl App {
|
|||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let system = SystemBuilder::new().window_title("Complicated Template").vsync(true).build()?;
|
||||
let config = DosLikeConfig::new().vsync(true);
|
||||
let system = SystemBuilder::new().window_title("Complicated Template").build(config)?;
|
||||
let app = App::new(system)?;
|
||||
main_loop(app, DemoState).context("Main loop error")
|
||||
}
|
||||
|
|
|
@ -6,22 +6,23 @@ use ggdt::system::*;
|
|||
use ggdt::utils::rnd_value;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut system = SystemBuilder::new().window_title("Minimal Template").vsync(true).build()?;
|
||||
let config = DosLikeConfig::new().vsync(true);
|
||||
let mut system = SystemBuilder::new().window_title("Minimal Template").build(config)?;
|
||||
|
||||
let font = BitmaskFont::new_vga_font()?;
|
||||
system.res.video.clear(0);
|
||||
system.res.video.print_string("Hello, world!", 20, 20, FontRenderOpts::Color(15), &system.res.font);
|
||||
|
||||
system.video.clear(0);
|
||||
system.video.print_string("Hello, world!", 20, 20, FontRenderOpts::Color(15), &font);
|
||||
|
||||
while !system.do_events() {
|
||||
if system.input_devices.keyboard.is_key_pressed(Scancode::Escape) {
|
||||
while !system.do_events()? {
|
||||
if system.res.keyboard.is_key_pressed(Scancode::Escape) {
|
||||
break;
|
||||
}
|
||||
|
||||
system.update()?;
|
||||
|
||||
let x = rnd_value(0, SCREEN_RIGHT) as i32;
|
||||
let y = rnd_value(0, SCREEN_BOTTOM) as i32;
|
||||
let color = rnd_value(0, 255);
|
||||
system.video.set_pixel(x, y, color);
|
||||
system.res.video.set_pixel(x, y, color);
|
||||
|
||||
system.display()?;
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
//! pub enum Event { /* .. various events here .. */ }
|
||||
//! struct App {
|
||||
//! pub delta: f32,
|
||||
//! pub system: ggdt::system::System,
|
||||
//! pub system: ggdt::system::System<ggdt::system::DosLike>,
|
||||
//! pub entities: ggdt::entities::Entities,
|
||||
//! pub component_systems: ggdt::entities::ComponentSystems<App, App>, // oh no! :'(
|
||||
//! pub event_publisher: ggdt::events::EventPublisher<Event>,
|
||||
|
@ -42,7 +42,7 @@
|
|||
//! // "core" because what the heck else do i call this? "InnerContext"? "InnerApp"? ...
|
||||
//! struct Core {
|
||||
//! pub delta: f32,
|
||||
//! pub system: ggdt::system::System,
|
||||
//! pub system: ggdt::system::System<ggdt::system::DosLike>,
|
||||
//! pub entities: ggdt::entities::Entities,
|
||||
//! pub event_publisher: ggdt::events::EventPublisher<Event>,
|
||||
//! }
|
||||
|
@ -81,7 +81,7 @@
|
|||
//! // with. you'd probably want to put your game/app resources/assets on this struct too.
|
||||
//! struct Core {
|
||||
//! pub delta: f32,
|
||||
//! pub system: ggdt::system::System,
|
||||
//! pub system: ggdt::system::System<ggdt::system::DosLike>,
|
||||
//! pub entities: ggdt::entities::Entities,
|
||||
//! pub event_publisher: ggdt::events::EventPublisher<Event>,
|
||||
//! }
|
||||
|
@ -129,9 +129,10 @@ use crate::events::*;
|
|||
use crate::states::*;
|
||||
use crate::system::*;
|
||||
|
||||
pub trait CoreState {
|
||||
fn system(&self) -> &System;
|
||||
fn system_mut(&mut self) -> &mut System;
|
||||
pub trait CoreState<SystemResType>
|
||||
where SystemResType: SystemResources {
|
||||
fn system(&self) -> &System<SystemResType>;
|
||||
fn system_mut(&mut self) -> &mut System<SystemResType>;
|
||||
|
||||
fn delta(&self) -> f32;
|
||||
fn set_delta(&mut self, delta: f32);
|
||||
|
@ -145,15 +146,17 @@ pub trait CoreState {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait CoreStateWithEvents<EventType>: CoreState {
|
||||
pub trait CoreStateWithEvents<SystemResType, EventType>: CoreState<SystemResType>
|
||||
where SystemResType: SystemResources {
|
||||
fn event_publisher(&mut self) -> &mut EventPublisher<EventType>;
|
||||
}
|
||||
|
||||
pub trait SupportSystems {}
|
||||
|
||||
pub trait SupportSystemsWithEvents<EventType>: SupportSystems
|
||||
pub trait SupportSystemsWithEvents<SystemResType, EventType>: SupportSystems
|
||||
where SystemResType: SystemResources
|
||||
{
|
||||
type ContextType: CoreStateWithEvents<EventType>;
|
||||
type ContextType: CoreStateWithEvents<SystemResType, EventType>;
|
||||
fn event_listeners(&mut self) -> &mut EventListeners<EventType, Self::ContextType>;
|
||||
|
||||
fn do_events(&mut self, context: &mut Self::ContextType) {
|
||||
|
@ -162,8 +165,9 @@ pub trait SupportSystemsWithEvents<EventType>: SupportSystems
|
|||
}
|
||||
}
|
||||
|
||||
pub trait AppContext {
|
||||
type CoreType: CoreState;
|
||||
pub trait AppContext<SystemResType>
|
||||
where SystemResType: SystemResources {
|
||||
type CoreType: CoreState<SystemResType>;
|
||||
type SupportType: SupportSystems;
|
||||
|
||||
fn core(&mut self) -> &mut Self::CoreType;
|
||||
|
@ -182,23 +186,24 @@ pub enum MainLoopError {
|
|||
AudioDeviceError(#[from] AudioDeviceError),
|
||||
}
|
||||
|
||||
pub fn main_loop<ContextType, State>(
|
||||
pub fn main_loop<SystemResType, ContextType, State>(
|
||||
mut app: ContextType,
|
||||
initial_state: State,
|
||||
) -> Result<(), MainLoopError>
|
||||
where
|
||||
ContextType: AppContext,
|
||||
State: AppState<ContextType> + 'static,
|
||||
where
|
||||
SystemResType: SystemResources,
|
||||
ContextType: AppContext<SystemResType>,
|
||||
State: AppState<ContextType> + 'static,
|
||||
{
|
||||
let mut states = States::new();
|
||||
states.push(initial_state)?;
|
||||
|
||||
let mut last_ticks = app.core().system().ticks();
|
||||
|
||||
while !app.core().system_mut().do_events() && !states.is_empty() {
|
||||
while !app.core().system_mut().do_events()? && !states.is_empty() {
|
||||
last_ticks = app.core().update_frame_delta(last_ticks);
|
||||
states.update(&mut app)?;
|
||||
app.core().system_mut().apply_audio_queue()?;
|
||||
app.core().system_mut().update()?;
|
||||
states.render(&mut app);
|
||||
app.core().system_mut().display()?;
|
||||
}
|
||||
|
|
|
@ -15,9 +15,12 @@ pub use self::event::*;
|
|||
pub use self::input_devices::*;
|
||||
pub use self::input_devices::keyboard::*;
|
||||
pub use self::input_devices::mouse::*;
|
||||
pub use self::res::*;
|
||||
pub use self::res::dos_like::*;
|
||||
|
||||
pub mod event;
|
||||
pub mod input_devices;
|
||||
pub mod res;
|
||||
|
||||
fn is_x11_compositor_skipping_problematic() -> bool {
|
||||
/*
|
||||
|
@ -58,19 +61,18 @@ pub enum SystemError {
|
|||
|
||||
#[error("System audio error: {0}")]
|
||||
AudioError(#[from] AudioError),
|
||||
|
||||
#[error("SystemResources error: {0}")]
|
||||
SystemResourcesError(#[from] SystemResourcesError),
|
||||
}
|
||||
|
||||
/// Builder for configuring and constructing an instance of [`System`].
|
||||
#[derive(Debug)]
|
||||
pub struct SystemBuilder {
|
||||
window_title: String,
|
||||
vsync: bool,
|
||||
target_framerate: Option<u32>,
|
||||
initial_scale_factor: u32,
|
||||
resizable: bool,
|
||||
show_mouse: bool,
|
||||
relative_mouse_scaling: bool,
|
||||
integer_scaling: bool,
|
||||
skip_x11_compositor: bool,
|
||||
}
|
||||
|
||||
|
@ -79,13 +81,9 @@ impl SystemBuilder {
|
|||
pub fn new() -> SystemBuilder {
|
||||
SystemBuilder {
|
||||
window_title: String::new(),
|
||||
vsync: false,
|
||||
target_framerate: None,
|
||||
initial_scale_factor: DEFAULT_SCALE_FACTOR,
|
||||
resizable: true,
|
||||
show_mouse: false,
|
||||
relative_mouse_scaling: true,
|
||||
integer_scaling: false,
|
||||
skip_x11_compositor: !is_x11_compositor_skipping_problematic(),
|
||||
}
|
||||
}
|
||||
|
@ -96,30 +94,6 @@ impl SystemBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
/// Enables or disables V-Sync for the [`System`] to be built. Enabling V-sync automatically
|
||||
/// disables `target_framerate`.
|
||||
pub fn vsync(&mut self, enable: bool) -> &mut SystemBuilder {
|
||||
self.vsync = enable;
|
||||
self.target_framerate = None;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a target framerate for the [`System`] being built to run at. This is intended to be
|
||||
/// used when V-sync is not desired, so setting a target framerate automatically disables
|
||||
/// `vsync`.
|
||||
pub fn target_framerate(&mut self, target_framerate: u32) -> &mut SystemBuilder {
|
||||
self.target_framerate = Some(target_framerate);
|
||||
self.vsync = false;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets an integer scaling factor for the [`System`] being built to up-scale the virtual
|
||||
/// framebuffer to when displaying it on screen.
|
||||
pub fn scale_factor(&mut self, scale_factor: u32) -> &mut SystemBuilder {
|
||||
self.initial_scale_factor = scale_factor;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets whether the window will be resizable by the user for the [`System`] being built.
|
||||
pub fn resizable(&mut self, enable: bool) -> &mut SystemBuilder {
|
||||
self.resizable = enable;
|
||||
|
@ -141,13 +115,6 @@ impl SystemBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
/// Enables or disables restricting the final rendered output to always be integer scaled,
|
||||
/// even if that result will not fully fill the area of the window.
|
||||
pub fn integer_scaling(&mut self, enable: bool) -> &mut SystemBuilder {
|
||||
self.integer_scaling = enable;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables or disables skipping the X11 server compositor on Linux systems only. This can be
|
||||
/// set to manually control the underlying SDL hint that is used to control this setting. The
|
||||
/// default setting that [`SystemBuilder`] configures is to follow the SDL default, except where
|
||||
|
@ -159,12 +126,10 @@ impl SystemBuilder {
|
|||
}
|
||||
|
||||
/// Builds and returns a [`System`] from the current configuration.
|
||||
pub fn build(&self) -> Result<System, SystemError> {
|
||||
// todo: maybe let this be customized in the future, or at least halved so a 160x120 mode can be available ... ?
|
||||
let screen_width = SCREEN_WIDTH;
|
||||
let screen_height = SCREEN_HEIGHT;
|
||||
let texture_pixel_size = 4; // 32-bit ARGB format
|
||||
|
||||
pub fn build<ConfigType: SystemResourcesConfig>(
|
||||
&self,
|
||||
config: ConfigType,
|
||||
) -> Result<System<ConfigType::SystemResourcesType>, SystemError> {
|
||||
sdl2::hint::set(
|
||||
"SDL_MOUSE_RELATIVE_SCALING",
|
||||
if self.relative_mouse_scaling {
|
||||
|
@ -210,14 +175,13 @@ impl SystemBuilder {
|
|||
Err(message) => return Err(SystemError::SDLError(message)),
|
||||
};
|
||||
|
||||
// create the window
|
||||
// create the window with an initial default size that will be overridden during
|
||||
// SystemResources initialization
|
||||
|
||||
let window_width = screen_width * self.initial_scale_factor;
|
||||
let window_height = screen_height * self.initial_scale_factor;
|
||||
let mut window_builder = &mut (sdl_video_subsystem.window(
|
||||
self.window_title.as_str(),
|
||||
window_width,
|
||||
window_height,
|
||||
640,
|
||||
480,
|
||||
));
|
||||
if self.resizable {
|
||||
window_builder = window_builder.resizable();
|
||||
|
@ -229,113 +193,24 @@ impl SystemBuilder {
|
|||
|
||||
sdl_context.mouse().show_cursor(self.show_mouse);
|
||||
|
||||
// turn the window into a canvas (under the hood, an SDL Renderer that owns the window)
|
||||
|
||||
let mut canvas_builder = sdl_window.into_canvas();
|
||||
if self.vsync {
|
||||
canvas_builder = canvas_builder.present_vsync();
|
||||
}
|
||||
let mut sdl_canvas = match canvas_builder.build() {
|
||||
Ok(canvas) => canvas,
|
||||
Err(error) => return Err(SystemError::SDLError(error.to_string())),
|
||||
};
|
||||
if let Err(error) = sdl_canvas.set_logical_size(screen_width, screen_height) {
|
||||
return Err(SystemError::SDLError(error.to_string()));
|
||||
};
|
||||
|
||||
// TODO: newer versions of rust-sdl2 support this directly off the WindowCanvas struct
|
||||
unsafe {
|
||||
sdl2::sys::SDL_RenderSetIntegerScale(
|
||||
sdl_canvas.raw(),
|
||||
if self.integer_scaling {
|
||||
sdl2::sys::SDL_bool::SDL_TRUE
|
||||
} else {
|
||||
sdl2::sys::SDL_bool::SDL_FALSE
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// create an SDL texture which we will be uploading to every frame to display the
|
||||
// application's framebuffer
|
||||
|
||||
let sdl_texture = match sdl_canvas.create_texture_streaming(
|
||||
Some(PixelFormatEnum::ARGB8888),
|
||||
screen_width,
|
||||
screen_height,
|
||||
let system_resources = match config.build(
|
||||
&sdl_video_subsystem,
|
||||
&sdl_audio_subsystem,
|
||||
sdl_window
|
||||
) {
|
||||
Ok(texture) => texture,
|
||||
Err(error) => return Err(SystemError::SDLError(error.to_string())),
|
||||
Ok(system_resources) => system_resources,
|
||||
Err(error) => return Err(SystemError::SystemResourcesError(error)),
|
||||
};
|
||||
let sdl_texture_pitch = (sdl_texture.query().width * texture_pixel_size) as usize;
|
||||
|
||||
// create a raw 32-bit RGBA buffer that will be used as the temporary source for
|
||||
// SDL texture uploads each frame. necessary as applications are dealing with 8-bit indexed
|
||||
// bitmaps, not 32-bit RGBA pixels, so this temporary buffer is where we convert the final
|
||||
// application framebuffer to 32-bit RGBA pixels before it is uploaded to the SDL texture
|
||||
let texture_pixels_size = (screen_width * screen_height * texture_pixel_size) as usize;
|
||||
let texture_pixels = vec![0u32; texture_pixels_size].into_boxed_slice();
|
||||
|
||||
// create the Bitmap object that will be exposed to the application acting as the system
|
||||
// backbuffer
|
||||
|
||||
let framebuffer = match Bitmap::new(SCREEN_WIDTH, SCREEN_HEIGHT) {
|
||||
Ok(bmp) => bmp,
|
||||
Err(error) => return Err(SystemError::SDLError(error.to_string())),
|
||||
};
|
||||
|
||||
// create the default palette, initialized to the VGA default palette. also exposed to the
|
||||
// application for manipulation
|
||||
|
||||
let palette = match Palette::new_vga_palette() {
|
||||
Ok(palette) => palette,
|
||||
Err(error) => return Err(SystemError::SDLError(error.to_string())),
|
||||
};
|
||||
|
||||
// create the default font, initialized to the VGA BIOS default font.
|
||||
|
||||
let font = match BitmaskFont::new_vga_font() {
|
||||
Ok(font) => font,
|
||||
Err(error) => return Err(SystemError::SDLError(error.to_string())),
|
||||
};
|
||||
|
||||
let audio_spec = AudioSpecDesired {
|
||||
freq: Some(TARGET_AUDIO_FREQUENCY as i32),
|
||||
channels: Some(TARGET_AUDIO_CHANNELS),
|
||||
samples: None,
|
||||
};
|
||||
let mut audio = Audio::new(audio_spec, &sdl_audio_subsystem)?;
|
||||
audio.resume();
|
||||
let audio_queue = AudioQueue::new(&audio);
|
||||
|
||||
let event_pump = SystemEventPump::from(sdl_event_pump);
|
||||
|
||||
// create input device objects, exposed to the application
|
||||
|
||||
let keyboard = Keyboard::new();
|
||||
let mouse = Mouse::new();
|
||||
|
||||
let input_devices = InputDevices {
|
||||
keyboard,
|
||||
mouse,
|
||||
};
|
||||
|
||||
Ok(System {
|
||||
sdl_context,
|
||||
sdl_audio_subsystem,
|
||||
sdl_video_subsystem,
|
||||
sdl_timer_subsystem,
|
||||
sdl_canvas,
|
||||
sdl_texture,
|
||||
sdl_texture_pitch,
|
||||
texture_pixels,
|
||||
audio,
|
||||
audio_queue,
|
||||
video: framebuffer,
|
||||
palette,
|
||||
font,
|
||||
input_devices,
|
||||
res: system_resources,
|
||||
event_pump,
|
||||
target_framerate: self.target_framerate,
|
||||
target_framerate_delta: None,
|
||||
next_tick: 0,
|
||||
})
|
||||
|
@ -346,101 +221,45 @@ impl SystemBuilder {
|
|||
/// applications to render to the display, react to input device events, etc. through the
|
||||
/// "virtual machine" exposed by this library.
|
||||
#[allow(dead_code)]
|
||||
pub struct System {
|
||||
pub struct System<SystemResType>
|
||||
where SystemResType: SystemResources {
|
||||
sdl_context: Sdl,
|
||||
sdl_audio_subsystem: AudioSubsystem,
|
||||
sdl_video_subsystem: VideoSubsystem,
|
||||
sdl_timer_subsystem: TimerSubsystem,
|
||||
sdl_canvas: WindowCanvas,
|
||||
sdl_texture: Texture,
|
||||
sdl_texture_pitch: usize,
|
||||
|
||||
texture_pixels: Box<[u32]>,
|
||||
|
||||
target_framerate: Option<u32>,
|
||||
target_framerate_delta: Option<i64>,
|
||||
next_tick: i64,
|
||||
|
||||
/// An [`Audio`] instance that allows interacting with the system's audio output device.
|
||||
pub audio: Audio,
|
||||
|
||||
/// An [`AudioQueue`] instance that can queue up playback/stop commands to be issued to the
|
||||
/// system's [`Audio`] instance a bit more flexibly. If you use this, your application must
|
||||
/// manually call [`AudioQueue::apply`] or [`AudioQueue::apply_to_device`] in your loop to
|
||||
/// flush the queued commands, otherwise this queue will not do anything.
|
||||
pub audio_queue: AudioQueue,
|
||||
|
||||
/// The primary backbuffer [`Bitmap`] that will be rendered to the screen whenever
|
||||
/// [`System::display`] is called. Regardless of the actual window size, this bitmap is always
|
||||
/// [`SCREEN_WIDTH`]x[`SCREEN_HEIGHT`] pixels in size.
|
||||
pub video: Bitmap,
|
||||
|
||||
/// The [`Palette`] that will be used in conjunction with the `video` backbuffer to
|
||||
/// render the final output to the screen whenever [`System::display`] is called.
|
||||
pub palette: Palette,
|
||||
|
||||
/// A pre-loaded [`Font`] that can be used for text rendering.
|
||||
pub font: BitmaskFont,
|
||||
|
||||
/// Contains instances representing the current state of the input devices available.
|
||||
/// To ensure these are updated each frame, ensure that you are either calling
|
||||
/// [`System::do_events`] or manually implementing an event polling loop which calls
|
||||
/// [`InputDevices::update`] and [`InputDevices::handle_event`].
|
||||
pub input_devices: InputDevices,
|
||||
pub res: SystemResType,
|
||||
|
||||
pub event_pump: SystemEventPump,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for System {
|
||||
impl<SystemResType> std::fmt::Debug for System<SystemResType>
|
||||
where SystemResType: SystemResources {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("System")
|
||||
.field("audio", &self.audio)
|
||||
.field("audio_queue", &self.audio_queue)
|
||||
.field("video", &self.video)
|
||||
.field("palette", &self.palette)
|
||||
.field("font", &self.font)
|
||||
//.field("keyboard", &self.keyboard)
|
||||
//.field("mouse", &self.mouse)
|
||||
.field("target_framerate", &self.target_framerate)
|
||||
.field("res", &self.res)
|
||||
.field("target_framerate_delta", &self.target_framerate_delta)
|
||||
.field("next_tick", &self.next_tick)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl System {
|
||||
impl<SystemResType> System<SystemResType>
|
||||
where SystemResType: SystemResources {
|
||||
/// Takes the `video` backbuffer bitmap and `palette` and renders it to the window, up-scaled
|
||||
/// to fill the window (preserving aspect ratio of course). If V-sync is enabled, this method
|
||||
/// will block to wait for V-sync. Otherwise, if a target framerate was configured a delay
|
||||
/// might be used to try to meet that framerate.
|
||||
pub fn display(&mut self) -> Result<(), SystemError> {
|
||||
self.input_devices.mouse.render_cursor(&mut self.video);
|
||||
|
||||
// convert application framebuffer to 32-bit RGBA pixels, and then upload it to the SDL
|
||||
// texture so it will be displayed on screen
|
||||
|
||||
self.video
|
||||
.copy_as_argb_to(&mut self.texture_pixels, &self.palette);
|
||||
|
||||
let texture_pixels = self.texture_pixels.as_byte_slice();
|
||||
if let Err(error) = self
|
||||
.sdl_texture
|
||||
.update(None, texture_pixels, self.sdl_texture_pitch)
|
||||
{
|
||||
return Err(SystemError::SDLError(error.to_string()));
|
||||
}
|
||||
self.sdl_canvas.clear();
|
||||
if let Err(error) = self.sdl_canvas.copy(&self.sdl_texture, None, None) {
|
||||
return Err(SystemError::SDLError(error));
|
||||
}
|
||||
self.sdl_canvas.present();
|
||||
|
||||
self.input_devices.mouse.hide_cursor(&mut self.video);
|
||||
self.res.display()?;
|
||||
|
||||
// if a specific target framerate is desired, apply some loop timing/delay to achieve it
|
||||
// TODO: do this better. delaying when running faster like this is a poor way to do this..
|
||||
|
||||
if let Some(target_framerate) = self.target_framerate {
|
||||
if let Some(target_framerate) = self.res.target_framerate() {
|
||||
if self.target_framerate_delta.is_some() {
|
||||
// normal path for every other loop iteration except the first
|
||||
let delay = self.next_tick - self.ticks() as i64;
|
||||
|
@ -457,8 +276,7 @@ impl System {
|
|||
// target_framerate_delta was not yet set to avoid doing any delay on the first
|
||||
// loop, just in case there was some other processing between the System struct
|
||||
// being created and the actual beginning of the first loop ...
|
||||
self.target_framerate_delta =
|
||||
Some((self.tick_frequency() / target_framerate as u64) as i64);
|
||||
self.target_framerate_delta = Some((self.tick_frequency() / target_framerate as u64) as i64);
|
||||
}
|
||||
|
||||
// expected time for the next display() call to happen by
|
||||
|
@ -476,9 +294,10 @@ impl System {
|
|||
/// ```no_run
|
||||
/// use ggdt::system::*;
|
||||
///
|
||||
/// let mut system = SystemBuilder::new().window_title("Example").build().unwrap();
|
||||
/// let config = DosLikeConfig::new();
|
||||
/// let mut system = SystemBuilder::new().window_title("Example").build(config).unwrap();
|
||||
///
|
||||
/// while !system.do_events() {
|
||||
/// while !system.do_events().unwrap() {
|
||||
/// // ... the body of your main loop here ...
|
||||
/// }
|
||||
/// ```
|
||||
|
@ -490,12 +309,13 @@ impl System {
|
|||
/// ```no_run
|
||||
/// use ggdt::system::*;
|
||||
///
|
||||
/// let mut system = SystemBuilder::new().window_title("Example").build().unwrap();
|
||||
/// let config = DosLikeConfig::new();
|
||||
/// let mut system = SystemBuilder::new().window_title("Example").build(config).unwrap();
|
||||
///
|
||||
/// 'mainloop: loop {
|
||||
/// system.input_devices.update();
|
||||
/// system.res.update_event_state().unwrap();
|
||||
/// for event in system.event_pump.poll_iter() {
|
||||
/// system.input_devices.handle_event(&event);
|
||||
/// system.res.handle_event(&event).unwrap();
|
||||
/// match event {
|
||||
/// SystemEvent::Quit => {
|
||||
/// break 'mainloop
|
||||
|
@ -507,23 +327,23 @@ impl System {
|
|||
/// // ...the rest of the body of your main loop here ...
|
||||
/// }
|
||||
/// ```
|
||||
pub fn do_events(&mut self) -> bool {
|
||||
pub fn do_events(&mut self) -> Result<bool, SystemError> {
|
||||
let mut should_quit = false;
|
||||
self.input_devices.update();
|
||||
self.res.update_event_state()?;
|
||||
for event in self.event_pump.poll_iter() {
|
||||
self.input_devices.handle_event(&event);
|
||||
self.res.handle_event(&event)?;
|
||||
if event == SystemEvent::Quit {
|
||||
should_quit = true;
|
||||
}
|
||||
}
|
||||
should_quit
|
||||
Ok(should_quit)
|
||||
}
|
||||
|
||||
/// Convenience method that applies any [`AudioBuffer`]s that may have been queued up on
|
||||
/// [`System::audio_queue`] to the system audio device so that they will be played. Do not
|
||||
/// call this when you already have an active lock on an [`AudioDevice`].
|
||||
pub fn apply_audio_queue(&mut self) -> Result<(), AudioDeviceError> {
|
||||
self.audio_queue.apply(&mut self.audio)
|
||||
pub fn update(&mut self) -> Result<(), SystemError> {
|
||||
if let Err(error) = self.res.update() {
|
||||
return Err(SystemError::SystemResourcesError(error));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn ticks(&self) -> u64 {
|
||||
|
|
298
ggdt/src/system/res/dos_like.rs
Normal file
298
ggdt/src/system/res/dos_like.rs
Normal file
|
@ -0,0 +1,298 @@
|
|||
use sdl2::video::Window;
|
||||
|
||||
use crate::system::*;
|
||||
|
||||
pub struct DosLikeConfig {
|
||||
screen_width: u32,
|
||||
screen_height: u32,
|
||||
vsync: bool,
|
||||
target_framerate: Option<u32>,
|
||||
initial_scale_factor: u32,
|
||||
integer_scaling: bool,
|
||||
}
|
||||
|
||||
impl DosLikeConfig {
|
||||
pub fn new() -> Self {
|
||||
DosLikeConfig {
|
||||
screen_width: SCREEN_WIDTH,
|
||||
screen_height: SCREEN_HEIGHT,
|
||||
vsync: false,
|
||||
target_framerate: None,
|
||||
initial_scale_factor: DEFAULT_SCALE_FACTOR,
|
||||
integer_scaling: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables or disables V-Sync for the [`System`] to be built. Enabling V-sync automatically
|
||||
/// disables `target_framerate`.
|
||||
pub fn vsync(mut self, enable: bool) -> Self {
|
||||
self.vsync = enable;
|
||||
self.target_framerate = None;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a target framerate for the [`System`] being built to run at. This is intended to be
|
||||
/// used when V-sync is not desired, so setting a target framerate automatically disables
|
||||
/// `vsync`.
|
||||
pub fn target_framerate(mut self, target_framerate: u32) -> Self {
|
||||
self.target_framerate = Some(target_framerate);
|
||||
self.vsync = false;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets an integer scaling factor for the [`System`] being built to up-scale the virtual
|
||||
/// framebuffer to when displaying it on screen.
|
||||
pub fn scale_factor(mut self, scale_factor: u32) -> Self {
|
||||
self.initial_scale_factor = scale_factor;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables or disables restricting the final rendered output to always be integer scaled,
|
||||
/// even if that result will not fully fill the area of the window.
|
||||
pub fn integer_scaling(mut self, enable: bool) -> Self {
|
||||
self.integer_scaling = enable;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemResourcesConfig for DosLikeConfig {
|
||||
type SystemResourcesType = DosLike;
|
||||
|
||||
fn build(
|
||||
self,
|
||||
_video_subsystem: &VideoSubsystem,
|
||||
audio_subsystem: &AudioSubsystem,
|
||||
mut window: Window,
|
||||
) -> Result<Self::SystemResourcesType, SystemResourcesError> {
|
||||
let texture_pixel_size = 4; // 32-bit ARGB format
|
||||
|
||||
let window_width = self.screen_width * self.initial_scale_factor;
|
||||
let window_height = self.screen_height * self.initial_scale_factor;
|
||||
if let Err(error) = window.set_size(window_width, window_height) {
|
||||
return Err(SystemResourcesError::SDLError(error.to_string()));
|
||||
}
|
||||
|
||||
// turn the window into a canvas (under the hood, an SDL Renderer that owns the window)
|
||||
|
||||
let mut canvas_builder = window.into_canvas();
|
||||
if self.vsync {
|
||||
canvas_builder = canvas_builder.present_vsync();
|
||||
}
|
||||
let mut sdl_canvas = match canvas_builder.build() {
|
||||
Ok(canvas) => canvas,
|
||||
Err(error) => return Err(SystemResourcesError::SDLError(error.to_string())),
|
||||
};
|
||||
if let Err(error) = sdl_canvas.set_logical_size(self.screen_width, self.screen_height) {
|
||||
return Err(SystemResourcesError::SDLError(error.to_string()));
|
||||
};
|
||||
|
||||
// TODO: newer versions of rust-sdl2 support this directly off the WindowCanvas struct
|
||||
unsafe {
|
||||
sdl2::sys::SDL_RenderSetIntegerScale(
|
||||
sdl_canvas.raw(),
|
||||
if self.integer_scaling {
|
||||
sdl2::sys::SDL_bool::SDL_TRUE
|
||||
} else {
|
||||
sdl2::sys::SDL_bool::SDL_FALSE
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// create an SDL texture which we will be uploading to every frame to display the
|
||||
// application's framebuffer
|
||||
|
||||
let sdl_texture = match sdl_canvas.create_texture_streaming(
|
||||
Some(PixelFormatEnum::ARGB8888),
|
||||
self.screen_width,
|
||||
self.screen_height,
|
||||
) {
|
||||
Ok(texture) => texture,
|
||||
Err(error) => return Err(SystemResourcesError::SDLError(error.to_string())),
|
||||
};
|
||||
let sdl_texture_pitch = (sdl_texture.query().width * texture_pixel_size) as usize;
|
||||
|
||||
// create a raw 32-bit RGBA buffer that will be used as the temporary source for
|
||||
// SDL texture uploads each frame. necessary as applications are dealing with 8-bit indexed
|
||||
// bitmaps, not 32-bit RGBA pixels, so this temporary buffer is where we convert the final
|
||||
// application framebuffer to 32-bit RGBA pixels before it is uploaded to the SDL texture
|
||||
let texture_pixels_size = (self.screen_width * self.screen_height * texture_pixel_size) as usize;
|
||||
let texture_pixels = vec![0u32; texture_pixels_size].into_boxed_slice();
|
||||
|
||||
// create the Bitmap object that will be exposed to the application acting as the system
|
||||
// backbuffer
|
||||
|
||||
let framebuffer = match Bitmap::new(SCREEN_WIDTH, SCREEN_HEIGHT) {
|
||||
Ok(bmp) => bmp,
|
||||
Err(error) => return Err(SystemResourcesError::SDLError(error.to_string())),
|
||||
};
|
||||
|
||||
// create the default palette, initialized to the VGA default palette. also exposed to the
|
||||
// application for manipulation
|
||||
|
||||
let palette = match Palette::new_vga_palette() {
|
||||
Ok(palette) => palette,
|
||||
Err(error) => return Err(SystemResourcesError::SDLError(error.to_string())),
|
||||
};
|
||||
|
||||
// create the default font, initialized to the VGA BIOS default font.
|
||||
|
||||
let font = match BitmaskFont::new_vga_font() {
|
||||
Ok(font) => font,
|
||||
Err(error) => return Err(SystemResourcesError::SDLError(error.to_string())),
|
||||
};
|
||||
|
||||
// create audio device and queue
|
||||
|
||||
let audio_spec = AudioSpecDesired {
|
||||
freq: Some(TARGET_AUDIO_FREQUENCY as i32),
|
||||
channels: Some(TARGET_AUDIO_CHANNELS),
|
||||
samples: None,
|
||||
};
|
||||
let mut audio = Audio::new(audio_spec, &audio_subsystem)?;
|
||||
audio.resume();
|
||||
let audio_queue = AudioQueue::new(&audio);
|
||||
|
||||
// create all of the input device objects
|
||||
|
||||
let keyboard = Keyboard::new();
|
||||
let mouse = Mouse::new();
|
||||
|
||||
Ok(DosLike {
|
||||
sdl_canvas,
|
||||
sdl_texture,
|
||||
sdl_texture_pitch,
|
||||
texture_pixels,
|
||||
vsync: self.vsync,
|
||||
target_framerate: self.target_framerate,
|
||||
audio,
|
||||
audio_queue,
|
||||
palette,
|
||||
video: framebuffer,
|
||||
font,
|
||||
keyboard,
|
||||
mouse,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DosLike {
|
||||
sdl_canvas: WindowCanvas,
|
||||
sdl_texture: Texture,
|
||||
sdl_texture_pitch: usize,
|
||||
texture_pixels: Box<[u32]>,
|
||||
vsync: bool,
|
||||
target_framerate: Option<u32>,
|
||||
|
||||
/// An [`Audio`] instance that allows interacting with the system's audio output device.
|
||||
pub audio: Audio,
|
||||
|
||||
/// An [`AudioQueue`] instance that can queue up playback/stop commands to be issued to the
|
||||
/// system's [`Audio`] instance a bit more flexibly. If you use this, your application must
|
||||
/// manually call [`AudioQueue::apply`] or [`AudioQueue::apply_to_device`] in your loop to
|
||||
/// flush the queued commands, otherwise this queue will not do anything.
|
||||
pub audio_queue: AudioQueue,
|
||||
|
||||
/// The [`Palette`] that will be used in conjunction with the `video` backbuffer to
|
||||
/// render the final output to the screen whenever [`System::display`] is called.
|
||||
pub palette: Palette,
|
||||
|
||||
/// The primary backbuffer [`Bitmap`] that will be rendered to the screen whenever
|
||||
/// [`System::display`] is called. Regardless of the actual window size, this bitmap is always
|
||||
/// [`SCREEN_WIDTH`]x[`SCREEN_HEIGHT`] pixels in size.
|
||||
pub video: Bitmap,
|
||||
|
||||
/// A pre-loaded [`Font`] that can be used for text rendering.
|
||||
pub font: BitmaskFont,
|
||||
|
||||
/// The current keyboard state. To ensure it is updated each frame, you should call
|
||||
/// [`System::do_events`] or [`System::do_events_with`] each frame.
|
||||
pub keyboard: Keyboard,
|
||||
|
||||
/// The current mouse state. To ensure it is updated each frame, you should call
|
||||
/// [`System::do_events`] or [`System::do_events_with`] each frame.
|
||||
pub mouse: Mouse,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for DosLike {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("DosLike")
|
||||
.field("audio", &self.audio)
|
||||
.field("audio_queue", &self.audio_queue)
|
||||
.field("palette", &self.palette)
|
||||
.field("video", &self.video)
|
||||
.field("font", &self.font)
|
||||
.field("keyboard", &self.keyboard)
|
||||
.field("mouse", &self.mouse)
|
||||
.field("vsync", &self.vsync)
|
||||
.field("target_framerate", &self.target_framerate)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemResources for DosLike {
|
||||
fn update(&mut self) -> Result<(), SystemResourcesError> {
|
||||
match self.audio_queue.apply(&mut self.audio) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(error) => Err(SystemResourcesError::AudioDeviceError(error))
|
||||
}
|
||||
}
|
||||
|
||||
fn display(&mut self) -> Result<(), SystemResourcesError> {
|
||||
self.mouse.render_cursor(&mut self.video);
|
||||
|
||||
// convert application framebuffer to 32-bit RGBA pixels, and then upload it to the SDL
|
||||
// texture so it will be displayed on screen
|
||||
|
||||
self.video.copy_as_argb_to(&mut self.texture_pixels, &self.palette);
|
||||
|
||||
let texture_pixels = self.texture_pixels.as_byte_slice();
|
||||
if let Err(error) = self.sdl_texture.update(None, texture_pixels, self.sdl_texture_pitch) {
|
||||
return Err(SystemResourcesError::SDLError(error.to_string()));
|
||||
}
|
||||
self.sdl_canvas.clear();
|
||||
if let Err(error) = self.sdl_canvas.copy(&self.sdl_texture, None, None) {
|
||||
return Err(SystemResourcesError::SDLError(error));
|
||||
}
|
||||
self.sdl_canvas.present();
|
||||
|
||||
self.mouse.hide_cursor(&mut self.video);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_event_state(&mut self) -> Result<(), SystemResourcesError> {
|
||||
self.keyboard.update();
|
||||
self.mouse.update();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_event(&mut self, event: &SystemEvent) -> Result<bool, SystemResourcesError> {
|
||||
if self.keyboard.handle_event(event) {
|
||||
return Ok(true);
|
||||
}
|
||||
if self.mouse.handle_event(event) {
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn width(&self) -> u32 {
|
||||
self.video.width()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn height(&self) -> u32 {
|
||||
self.video.height()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn vsync(&self) -> bool {
|
||||
self.vsync
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn target_framerate(&self) -> Option<u32> {
|
||||
self.target_framerate
|
||||
}
|
||||
}
|
41
ggdt/src/system/res/mod.rs
Normal file
41
ggdt/src/system/res/mod.rs
Normal file
|
@ -0,0 +1,41 @@
|
|||
use thiserror::Error;
|
||||
|
||||
use crate::audio::{AudioDeviceError, AudioError};
|
||||
use crate::system::SystemEvent;
|
||||
|
||||
pub mod dos_like;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SystemResourcesError {
|
||||
#[error("SystemResources SDL error: {0}")]
|
||||
SDLError(String),
|
||||
|
||||
#[error("System audioerror: {0}")]
|
||||
AudioError(#[from] AudioError),
|
||||
|
||||
#[error("System audio device error: {0}")]
|
||||
AudioDeviceError(#[from] AudioDeviceError),
|
||||
}
|
||||
|
||||
pub trait SystemResourcesConfig {
|
||||
type SystemResourcesType: SystemResources;
|
||||
|
||||
fn build(
|
||||
self,
|
||||
video_subsystem: &sdl2::VideoSubsystem,
|
||||
audio_subsystem: &sdl2::AudioSubsystem,
|
||||
window: sdl2::video::Window,
|
||||
) -> Result<Self::SystemResourcesType, SystemResourcesError>;
|
||||
}
|
||||
|
||||
pub trait SystemResources : std::fmt::Debug {
|
||||
fn update(&mut self) -> Result<(), SystemResourcesError>;
|
||||
fn display(&mut self) -> Result<(), SystemResourcesError>;
|
||||
fn update_event_state(&mut self) -> Result<(), SystemResourcesError>;
|
||||
fn handle_event(&mut self, event: &SystemEvent) -> Result<bool, SystemResourcesError>;
|
||||
|
||||
fn width(&self) -> u32;
|
||||
fn height(&self) -> u32;
|
||||
fn vsync(&self) -> bool;
|
||||
fn target_framerate(&self) -> Option<u32>;
|
||||
}
|
Loading…
Reference in a new issue