Skip to content

Instantly share code, notes, and snippets.

@kyren
Created April 13, 2018 08:27
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save kyren/c21350b26eb3dbda547153b81dd808b4 to your computer and use it in GitHub Desktop.
use std::path::PathBuf;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::thread;
use std::os::raw::c_void;
use std::rc::Rc;
use std::cell::{Cell, RefCell};
use std::i16;
use std::time::Instant;
use failure::{err_msg, Error};
use strum::IntoEnumIterator;
use gl;
use sdl2;
use spell_core::math::Vector2;
use spell_core::util::{duration_from_seconds, duration_to_seconds, TickMonitor};
use spell_platform::{Application, ApplicationController, AudioCallback, AudioSpec, ControllerAxis,
ControllerButton, ControllerId, ControllerState, InputEvent, InputState, Key,
KeyModSet, MouseButton};
use opengl_rendering::GLRenderer;
const TARGET_FRAME_RATE: f64 = 60.0;
const TICK_MONITOR_WINDOW: f64 = 5.0 / 60.0;
/// Runs the given application with a hard-coded target framerate of 60hz. Doesn't work well if the
/// monitor refresh rate is not a multiple of 60, but that is exceptionally rare. We have to always
/// target 60hz, because even if the SDL2 API claims that there is an OpenGL swap interval, in
/// certain situations on certain platforms `gl_swap_window` will return nearly instantly and can be
/// called arbitrarily fast. We set a maximum framerate of 60hz so that in that case, we do not
/// just spin and always use 100% of a core.
pub fn run_application<T: Application>() {
let sdl_context = sdl2::init().unwrap();
let video_subsystem = sdl_context.video().unwrap();
let controller_subsystem = sdl_context.game_controller().unwrap();
let gl_attr = video_subsystem.gl_attr();
gl_attr.set_context_profile(sdl2::video::GLProfile::Core);
gl_attr.set_context_version(3, 3);
gl_attr.set_double_buffer(true);
gl_attr.set_depth_size(0);
// Right now, hard code preferring late swap tearing, then vsync, then no vsync.
if !video_subsystem.gl_set_swap_interval(-1) {
video_subsystem.gl_set_swap_interval(1);
}
let window = video_subsystem
.window("spellbound", 1200, 700)
.opengl()
.position_centered()
.resizable()
.build()
.unwrap();
let _gl_context = window.gl_create_context().unwrap();
gl::load_with(|s| video_subsystem.gl_get_proc_address(s) as *const c_void);
let event_pump = Rc::new(RefCell::new(sdl_context.event_pump().unwrap()));
// Process controller events
controller_subsystem.set_event_state(true);
let renderer = GLRenderer::new().unwrap();
let controller = Rc::new(SdlApplicationController {
audio_subsystem: sdl_context.audio().unwrap(),
audio_device: RefCell::new(None),
controller_subsystem,
controller_ids: RefCell::new(HashMap::new()),
controllers: RefCell::new(BTreeMap::new()),
window_height: Cell::new(0),
event_pump: event_pump.clone(),
text_input: video_subsystem.text_input(),
clipboard: video_subsystem.clipboard(),
quit: Cell::new(false),
});
controller.scan_new_controllers();
let base_path =
PathBuf::from(sdl2::filesystem::base_path().expect("could not find base application path"));
let mut application = T::init(&base_path, controller.clone(), renderer.clone());
let mut tick_monitor = TickMonitor::new_at_rate(TARGET_FRAME_RATE, TICK_MONITOR_WINDOW);
let mut last_frame_start: Option<Instant> = None;
'running: loop {
let (window_width, window_height) = window.size();
controller.window_height.set(window_height as i32);
renderer.set_screen_size(window_width, window_height);
let mut input_events = Vec::new();
for event in event_pump.borrow_mut().poll_iter() {
match event {
sdl2::event::Event::Quit { .. } => break 'running,
sdl2::event::Event::KeyDown {
scancode: Some(sc),
repeat,
..
} => {
// TODO: mods not properly set
if let Some(key) = get_spell_key(sc) {
input_events.push(InputEvent::KeyDown {
key: key,
mods: KeyModSet::empty(),
repeat: repeat,
});
}
}
sdl2::event::Event::KeyUp {
scancode: Some(sc), ..
} => if let Some(key) = get_spell_key(sc) {
input_events.push(InputEvent::KeyUp { key: key });
},
sdl2::event::Event::MouseButtonDown {
mouse_btn, x, y, ..
} => {
let mouse_position = Vector2::new(x, window_height as i32 - y);
if let Some(mouse_button) = get_spell_mouse_button(mouse_btn) {
input_events.push(InputEvent::MouseButtonDown {
button: mouse_button,
position: mouse_position,
});
}
}
sdl2::event::Event::MouseButtonUp {
mouse_btn, x, y, ..
} => {
let mouse_position = Vector2::new(x, window_height as i32 - y);
if let Some(mouse_button) = get_spell_mouse_button(mouse_btn) {
input_events.push(InputEvent::MouseButtonUp {
button: mouse_button,
position: mouse_position,
});
}
}
sdl2::event::Event::MouseMotion {
x, y, xrel, yrel, ..
} => {
let mouse_position = Vector2::new(x, window_height as i32 - y);
input_events.push(InputEvent::MouseMove {
movement: Vector2::new(xrel, -yrel),
position: mouse_position,
});
}
sdl2::event::Event::MouseWheel { x, y, .. } => {
input_events.push(InputEvent::MouseWheel {
scroll: Vector2::new(x, y),
});
}
sdl2::event::Event::TextInput { text, .. } => {
input_events.push(InputEvent::TextInput { text });
}
sdl2::event::Event::ControllerAxisMotion {
which, axis, value, ..
} => if let Some(&id) = controller.controller_ids.borrow().get(&which) {
let axis = get_spell_controller_axis(axis);
input_events.push(InputEvent::ControllerAxisChange {
controller_id: id,
axis,
value: fix_axis_direction(axis, value),
});
},
sdl2::event::Event::ControllerButtonDown { which, button, .. } => {
if let Some(&id) = controller.controller_ids.borrow().get(&which) {
input_events.push(InputEvent::ControllerButtonDown {
controller_id: id,
button: get_spell_controller_button(button),
});
}
}
sdl2::event::Event::ControllerButtonUp { which, button, .. } => {
if let Some(&id) = controller.controller_ids.borrow().get(&which) {
input_events.push(InputEvent::ControllerButtonUp {
controller_id: id,
button: get_spell_controller_button(button),
});
}
}
sdl2::event::Event::ControllerDeviceAdded { .. } => {
controller.scan_new_controllers();
}
sdl2::event::Event::ControllerDeviceRemoved { .. } => {
controller.filter_detached_controllers();
}
_ => {}
}
}
let dt = if let Some(last_frame_start) = last_frame_start {
duration_to_seconds(last_frame_start.elapsed())
} else {
1.0 / TARGET_FRAME_RATE
};
last_frame_start = Some(Instant::now());
application.update(dt, input_events);
tick_monitor.tick(1.0);
window.gl_swap_window();
let spare_time = tick_monitor.spare_time(TARGET_FRAME_RATE);
if spare_time > 0.0 {
thread::sleep(duration_from_seconds(spare_time));
}
}
application.uninit();
}
struct SdlAudioCallback(AudioCallback);
impl sdl2::audio::AudioCallback for SdlAudioCallback {
type Channel = f32;
fn callback(&mut self, buf: &mut [Self::Channel]) {
self.0(buf)
}
}
struct SdlApplicationController {
audio_subsystem: sdl2::AudioSubsystem,
audio_device: RefCell<Option<sdl2::audio::AudioDevice<SdlAudioCallback>>>,
controller_subsystem: sdl2::GameControllerSubsystem,
controller_ids: RefCell<HashMap<i32, ControllerId>>,
controllers: RefCell<BTreeMap<ControllerId, sdl2::controller::GameController>>,
window_height: Cell<i32>,
event_pump: Rc<RefCell<sdl2::EventPump>>,
text_input: sdl2::keyboard::TextInputUtil,
clipboard: sdl2::clipboard::ClipboardUtil,
quit: Cell<bool>,
}
impl SdlApplicationController {
fn scan_new_controllers(&self) {
let mut controller_ids = self.controller_ids.borrow_mut();
let mut controllers = self.controllers.borrow_mut();
for i in 0
..self.controller_subsystem
.num_joysticks()
.expect("could not enumerate controllers")
{
if self.controller_subsystem.is_game_controller(i) {
let controller = self.controller_subsystem
.open(i)
.expect("could not open controller");
let controller_id = ControllerId(
controllers
.iter()
.last()
.map(|(id, _)| id.0 + 1)
.unwrap_or(0),
);
controller_ids.insert(controller.instance_id(), controller_id);
controllers.insert(controller_id, controller).is_none();
}
}
}
fn filter_detached_controllers(&self) {
let mut controller_ids = self.controller_ids.borrow_mut();
let mut controllers = self.controllers.borrow_mut();
let mut detached_controllers = HashSet::new();
for (&id, controller) in controllers.iter() {
if !controller.attached() {
detached_controllers.insert(id);
}
}
for id in &detached_controllers {
controllers.remove(id);
}
controller_ids.retain(|_, id| !detached_controllers.contains(id));
}
}
impl ApplicationController for SdlApplicationController {
fn start_text_input(&self) {
self.text_input.start();
}
fn stop_text_input(&self) {
self.text_input.stop();
}
fn clipboard_text(&self) -> Result<String, Error> {
self.clipboard.clipboard_text().map_err(err_msg)
}
fn set_clipboard_text(&self, text: &str) -> Result<(), Error> {
Ok(self.clipboard.set_clipboard_text(text).map_err(err_msg)
}
fn input_state(&self) -> InputState {
let event_pump = self.event_pump.borrow();
let keyboard_state = event_pump.keyboard_state();
let mouse_state = event_pump.mouse_state();
let mouse_position =
Vector2::new(mouse_state.x(), self.window_height.get() - mouse_state.y());
let mut input_state = InputState {
keys_down: HashSet::new(),
mouse_buttons_down: HashSet::new(),
mouse_position,
controllers: BTreeMap::new(),
};
input_state.keys_down = keyboard_state
.pressed_scancodes()
.filter_map(get_spell_key)
.collect();
input_state.mouse_buttons_down = mouse_state
.mouse_buttons()
.filter_map(|(b, p)| if p { Some(b) } else { None })
.filter_map(get_spell_mouse_button)
.collect();
let controller_ids = self.controller_ids.borrow();
let mut controllers = self.controllers.borrow_mut();
for controller in controllers.values_mut() {
if controller.attached() {
let mut controller_state = ControllerState {
buttons_down: HashSet::new(),
axis_values: HashMap::new(),
};
for axis in ControllerAxis::iter() {
let value = controller.axis(get_sdl_controller_axis(axis));
if value != 0 {
controller_state
.axis_values
.insert(axis, fix_axis_direction(axis, value));
}
}
for button in ControllerButton::iter() {
if controller.button(get_sdl_controller_button(button)) {
controller_state.buttons_down.insert(button);
}
}
let controller_id = *controller_ids.get(&controller.instance_id()).unwrap();
input_state
.controllers
.insert(controller_id, controller_state);
}
}
input_state
}
fn start_audio(&self, get_callback: &mut FnMut(AudioSpec) -> AudioCallback) {
let mut device = self.audio_device.borrow_mut();
*device = None;
let audio_device = self.audio_subsystem
.open_playback(
None,
&sdl2::audio::AudioSpecDesired {
freq: Some(44100),
channels: Some(2),
samples: Some(2048),
},
|spec| {
SdlAudioCallback(get_callback(AudioSpec {
sample_rate: spec.freq as u32,
channels: spec.channels,
}))
},
)
.expect("could not open audio device for playback");
audio_device.resume();
*device = Some(audio_device);
}
fn stop_audio(&self) {
*self.audio_device.borrow_mut() = None;
}
fn quit(&self) {
self.quit.set(true);
}
}
fn get_spell_key(key: sdl2::keyboard::Scancode) -> Option<Key> {
// UNINTERESTING
}
fn get_spell_mouse_button(mouse_button: sdl2::mouse::MouseButton) -> Option<MouseButton> {
// UNINTERESTING
}
fn get_sdl_controller_button(controller_button: ControllerButton) -> sdl2::controller::Button {
// UNINTERESTING
}
fn get_spell_controller_button(controller_button: sdl2::controller::Button) -> ControllerButton {
// UNINTERESTING
}
fn get_sdl_controller_axis(controller_axis: ControllerAxis) -> sdl2::controller::Axis {
// UNINTERESTING
}
fn get_spell_controller_axis(controller_axis: sdl2::controller::Axis) -> ControllerAxis {
// UNINTERESTING
}
fn fix_axis_direction(axis: ControllerAxis, axis_value: i16) -> i16 {
// UNINTERESTING
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment