mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2024-11-21 21:22:44 +00:00
Compare commits
5 commits
b9a7d98684
...
0db8ab0308
Author | SHA1 | Date | |
---|---|---|---|
0db8ab0308 | |||
56832514eb | |||
1b7a64b5d9 | |||
d41fa57ca2 | |||
067376c74f |
|
@ -70,6 +70,7 @@ fern = "0.6.2"
|
|||
imgui = { git = "https://github.com/imgui-rs/imgui-rs.git", rev = "67f7f11363e62f09aa0e1288a17800e505860486" }
|
||||
image = { version = "0.24", default-features = false, features = ["png", "bmp"] }
|
||||
itertools = "0.10"
|
||||
include-flate = "0.3.0"
|
||||
lazy_static = "1.4"
|
||||
lewton = { version = "0.10", optional = true }
|
||||
log = "0.4"
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
use std::{fmt, io};
|
||||
use std::fmt::Debug;
|
||||
use std::io::Cursor;
|
||||
use std::io::ErrorKind;
|
||||
use std::io::SeekFrom;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
use std::{fmt, io};
|
||||
|
||||
use include_flate::flate;
|
||||
|
||||
use crate::framework::error::GameError::FilesystemError;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::vfs::{OpenOptions, VFile, VFS, VMetadata};
|
||||
use crate::framework::vfs::{OpenOptions, VFile, VMetadata, VFS};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BuiltinFile(Cursor<&'static [u8]>);
|
||||
|
@ -61,7 +63,7 @@ impl VMetadata for BuiltinMetadata {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
enum FSNode {
|
||||
File(&'static str, &'static [u8]),
|
||||
File(&'static str, fn() -> &'static [u8]),
|
||||
Directory(&'static str, Vec<FSNode>),
|
||||
}
|
||||
|
||||
|
@ -75,14 +77,14 @@ impl FSNode {
|
|||
|
||||
fn to_file(&self) -> GameResult<Box<dyn VFile>> {
|
||||
match self {
|
||||
FSNode::File(_, buf) => Ok(BuiltinFile::from(buf)),
|
||||
FSNode::File(_, accessor) => Ok(BuiltinFile::from(accessor())),
|
||||
FSNode::Directory(name, _) => Err(FilesystemError(format!("{} is a directory.", name))),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_metadata(&self) -> Box<dyn VMetadata> {
|
||||
match self {
|
||||
FSNode::File(_, buf) => Box::new(BuiltinMetadata { is_dir: false, size: buf.len() as u64 }),
|
||||
FSNode::File(_, accessor) => Box::new(BuiltinMetadata { is_dir: false, size: accessor().len() as u64 }),
|
||||
FSNode::Directory(_, _) => Box::new(BuiltinMetadata { is_dir: true, size: 0 }),
|
||||
}
|
||||
}
|
||||
|
@ -92,58 +94,58 @@ pub struct BuiltinFS {
|
|||
root: Vec<FSNode>,
|
||||
}
|
||||
|
||||
flate!(static BUILTIN_FONT_FNT: [u8] from "src/data/builtin/builtin_font.fnt");
|
||||
flate!(static GAMECONTROLLERDB_TXT: [u8] from "src/data/builtin/gamecontrollerdb.txt");
|
||||
flate!(static SUE_BMP: [u8] from "res/sue.bmp");
|
||||
flate!(static ORGANYA_WAVETABLE_DOUKUTSU_BIN: [u8] from "src/data/builtin/organya-wavetable-doukutsu.bin");
|
||||
flate!(static EN_JSON: [u8] from "src/data/builtin/builtin_data/locale/en.json");
|
||||
flate!(static JP_JSON: [u8] from "src/data/builtin/builtin_data/locale/jp.json");
|
||||
|
||||
impl BuiltinFS {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
root: vec![FSNode::Directory(
|
||||
"builtin",
|
||||
vec![
|
||||
FSNode::File("builtin_font.fnt", include_bytes!("builtin/builtin_font.fnt")),
|
||||
FSNode::File("builtin_font_0.png", include_bytes!("builtin/builtin_font_0.png")),
|
||||
FSNode::File("builtin_font_1.png", include_bytes!("builtin/builtin_font_1.png")),
|
||||
FSNode::File("gamecontrollerdb.txt", include_bytes!("builtin/gamecontrollerdb.txt")),
|
||||
FSNode::File("icon.bmp", include_bytes!("../../res/sue.bmp")),
|
||||
FSNode::File(
|
||||
"organya-wavetable-doukutsu.bin",
|
||||
include_bytes!("builtin/organya-wavetable-doukutsu.bin"),
|
||||
),
|
||||
FSNode::File("touch.png", include_bytes!("builtin/touch.png")),
|
||||
FSNode::File("builtin_font.fnt", || &BUILTIN_FONT_FNT),
|
||||
FSNode::File("builtin_font_0.png", || include_bytes!("builtin/builtin_font_0.png")),
|
||||
FSNode::File("builtin_font_1.png", || include_bytes!("builtin/builtin_font_1.png")),
|
||||
FSNode::File("gamecontrollerdb.txt", || &GAMECONTROLLERDB_TXT),
|
||||
FSNode::File("icon.bmp", || &SUE_BMP),
|
||||
FSNode::File("organya-wavetable-doukutsu.bin", || &ORGANYA_WAVETABLE_DOUKUTSU_BIN),
|
||||
FSNode::File("touch.png", || include_bytes!("builtin/touch.png")),
|
||||
FSNode::Directory(
|
||||
"builtin_data",
|
||||
vec![
|
||||
FSNode::File("buttons.png", include_bytes!("builtin/builtin_data/buttons.png")),
|
||||
FSNode::File("triangles.png", include_bytes!("builtin/builtin_data/triangles.png")),
|
||||
FSNode::File("buttons.png", || include_bytes!("builtin/builtin_data/buttons.png")),
|
||||
FSNode::File("triangles.png", || include_bytes!("builtin/builtin_data/triangles.png")),
|
||||
FSNode::Directory(
|
||||
"headband",
|
||||
vec![
|
||||
FSNode::Directory(
|
||||
"ogph",
|
||||
vec![
|
||||
FSNode::File(
|
||||
"Casts.png",
|
||||
include_bytes!("builtin/builtin_data/headband/ogph/Casts.png"),
|
||||
),
|
||||
FSNode::File("Casts.png", || {
|
||||
include_bytes!("builtin/builtin_data/headband/ogph/Casts.png")
|
||||
}),
|
||||
FSNode::Directory(
|
||||
"Npc",
|
||||
vec![
|
||||
FSNode::File(
|
||||
"NpcGuest.png",
|
||||
FSNode::File("NpcGuest.png", || {
|
||||
include_bytes!(
|
||||
"builtin/builtin_data/headband/ogph/Npc/NpcGuest.png"
|
||||
),
|
||||
),
|
||||
FSNode::File(
|
||||
"NpcMiza.png",
|
||||
)
|
||||
}),
|
||||
FSNode::File("NpcMiza.png", || {
|
||||
include_bytes!(
|
||||
"builtin/builtin_data/headband/ogph/Npc/NpcMiza.png"
|
||||
),
|
||||
),
|
||||
FSNode::File(
|
||||
"NpcRegu.png",
|
||||
)
|
||||
}),
|
||||
FSNode::File("NpcRegu.png", || {
|
||||
include_bytes!(
|
||||
"builtin/builtin_data/headband/ogph/Npc/NpcRegu.png"
|
||||
),
|
||||
),
|
||||
)
|
||||
}),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
@ -151,31 +153,27 @@ impl BuiltinFS {
|
|||
FSNode::Directory(
|
||||
"plus",
|
||||
vec![
|
||||
FSNode::File(
|
||||
"Casts.png",
|
||||
include_bytes!("builtin/builtin_data/headband/plus/casts.png"),
|
||||
),
|
||||
FSNode::File("Casts.png", || {
|
||||
include_bytes!("builtin/builtin_data/headband/plus/casts.png")
|
||||
}),
|
||||
FSNode::Directory(
|
||||
"Npc",
|
||||
vec![
|
||||
FSNode::File(
|
||||
"NpcGuest.png",
|
||||
FSNode::File("NpcGuest.png", || {
|
||||
include_bytes!(
|
||||
"builtin/builtin_data/headband/plus/npc/npcguest.png"
|
||||
),
|
||||
),
|
||||
FSNode::File(
|
||||
"NpcMiza.png",
|
||||
)
|
||||
}),
|
||||
FSNode::File("NpcMiza.png", || {
|
||||
include_bytes!(
|
||||
"builtin/builtin_data/headband/plus/npc/npcmiza.png"
|
||||
),
|
||||
),
|
||||
FSNode::File(
|
||||
"NpcRegu.png",
|
||||
)
|
||||
}),
|
||||
FSNode::File("NpcRegu.png", || {
|
||||
include_bytes!(
|
||||
"builtin/builtin_data/headband/plus/npc/npcregu.png"
|
||||
),
|
||||
),
|
||||
)
|
||||
}),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
@ -184,16 +182,13 @@ impl BuiltinFS {
|
|||
),
|
||||
FSNode::Directory(
|
||||
"locale",
|
||||
vec![
|
||||
FSNode::File("en.json", include_bytes!("builtin/builtin_data/locale/en.json")),
|
||||
FSNode::File("jp.json", include_bytes!("builtin/builtin_data/locale/jp.json")),
|
||||
],
|
||||
vec![FSNode::File("en.json", || &EN_JSON), FSNode::File("jp.json", || &JP_JSON)],
|
||||
),
|
||||
],
|
||||
),
|
||||
FSNode::Directory(
|
||||
"lightmap",
|
||||
vec![FSNode::File("spot.png", include_bytes!("builtin/lightmap/spot.png"))],
|
||||
vec![FSNode::File("spot.png", || include_bytes!("builtin/lightmap/spot.png"))],
|
||||
),
|
||||
],
|
||||
)],
|
||||
|
@ -278,7 +273,7 @@ impl VFS for BuiltinFS {
|
|||
self.get_node(path).map(|v| v.to_metadata())
|
||||
}
|
||||
|
||||
fn read_dir(&self, path: &Path) -> GameResult<Box<dyn Iterator<Item=GameResult<PathBuf>>>> {
|
||||
fn read_dir(&self, path: &Path) -> GameResult<Box<dyn Iterator<Item = GameResult<PathBuf>>>> {
|
||||
match self.get_node(path) {
|
||||
Ok(FSNode::Directory(_, contents)) => {
|
||||
let mut vec = Vec::new();
|
||||
|
@ -302,15 +297,15 @@ impl VFS for BuiltinFS {
|
|||
fn test_builtin_fs() {
|
||||
let fs = BuiltinFS {
|
||||
root: vec![
|
||||
FSNode::File("test.txt", &[]),
|
||||
FSNode::File("test.txt", || &[]),
|
||||
FSNode::Directory(
|
||||
"memes",
|
||||
vec![
|
||||
FSNode::File("nothing.txt", &[]),
|
||||
FSNode::Directory("secret stuff", vec![FSNode::File("passwords.txt", b"12345678")]),
|
||||
FSNode::File("nothing.txt", || &[]),
|
||||
FSNode::Directory("secret stuff", vec![FSNode::File("passwords.txt", || b"12345678")]),
|
||||
],
|
||||
),
|
||||
FSNode::File("test2.txt", &[]),
|
||||
FSNode::File("test2.txt", || &[]),
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
@ -3,10 +3,12 @@ use std::rc::Rc;
|
|||
|
||||
use imgui::DrawData;
|
||||
|
||||
use super::context::Context;
|
||||
use super::error::GameResult;
|
||||
use super::graphics::BlendMode;
|
||||
use super::graphics::SwapMode;
|
||||
|
||||
use crate::common::{Color, Rect};
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics::{BlendMode, VSyncMode};
|
||||
use crate::game::Game;
|
||||
|
||||
#[repr(C)]
|
||||
|
@ -46,7 +48,7 @@ pub trait BackendRenderer {
|
|||
|
||||
fn present(&mut self) -> GameResult;
|
||||
|
||||
fn set_vsync_mode(&mut self, _mode: VSyncMode) -> GameResult {
|
||||
fn set_swap_mode(&mut self, _mode: SwapMode) -> GameResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -107,26 +109,26 @@ pub trait BackendGamepad {
|
|||
#[allow(unreachable_code)]
|
||||
pub fn init_backend(headless: bool, size_hint: (u16, u16)) -> GameResult<Box<dyn Backend>> {
|
||||
if headless {
|
||||
return crate::framework::backend_null::NullBackend::new();
|
||||
return super::backend_null::NullBackend::new();
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "backend-horizon"))]
|
||||
{
|
||||
return crate::framework::backend_horizon::HorizonBackend::new();
|
||||
return super::backend_horizon::HorizonBackend::new();
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "backend-winit"))]
|
||||
{
|
||||
return crate::framework::backend_winit::WinitBackend::new();
|
||||
return super::backend_winit::WinitBackend::new();
|
||||
}
|
||||
|
||||
#[cfg(feature = "backend-sdl")]
|
||||
{
|
||||
return crate::framework::backend_sdl2::SDL2Backend::new(size_hint);
|
||||
return super::backend_sdl2::SDL2Backend::new(size_hint);
|
||||
}
|
||||
|
||||
log::warn!("No backend compiled in, using null backend instead.");
|
||||
crate::framework::backend_null::NullBackend::new()
|
||||
super::backend_null::NullBackend::new()
|
||||
}
|
||||
|
||||
pub enum SpriteBatchCommand {
|
||||
|
|
|
@ -32,13 +32,13 @@ use crate::framework::backend::{
|
|||
};
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::{GameError, GameResult};
|
||||
use crate::framework::filesystem;
|
||||
use crate::framework::gamepad::{Axis, Button, GamepadType};
|
||||
use crate::framework::graphics::BlendMode;
|
||||
use crate::framework::graphics::{BlendMode, SwapMode};
|
||||
use crate::framework::keyboard::ScanCode;
|
||||
#[cfg(feature = "render-opengl")]
|
||||
use crate::framework::render_opengl::{GLContext, OpenGLRenderer};
|
||||
use crate::framework::ui::init_imgui;
|
||||
use crate::framework::{filesystem, render_opengl};
|
||||
use crate::game::shared_game_state::WindowMode;
|
||||
use crate::game::Game;
|
||||
use crate::game::GAME_SUSPENDED;
|
||||
|
@ -465,37 +465,39 @@ impl BackendEventLoop for SDL2EventLoop {
|
|||
key_map[ImGuiKey_Delete as usize] = Scancode::Delete as u32;
|
||||
key_map[ImGuiKey_Enter as usize] = Scancode::Return as u32;
|
||||
|
||||
let refs = self.refs.clone();
|
||||
struct SDL2GLPlatform(Rc<RefCell<SDL2Context>>);
|
||||
|
||||
let user_data = Rc::into_raw(refs) as *mut c_void;
|
||||
|
||||
unsafe fn get_proc_address(user_data: &mut *mut c_void, name: &str) -> *const c_void {
|
||||
let refs = Rc::from_raw(*user_data as *mut RefCell<SDL2Context>);
|
||||
|
||||
let result = {
|
||||
let refs = &mut *refs.as_ptr();
|
||||
impl render_opengl::GLPlatformFunctions for SDL2GLPlatform {
|
||||
fn get_proc_address(&self, name: &str) -> *const c_void {
|
||||
let refs = self.0.borrow();
|
||||
refs.video.gl_get_proc_address(name) as *const _
|
||||
};
|
||||
|
||||
*user_data = Rc::into_raw(refs) as *mut c_void;
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
unsafe fn swap_buffers(user_data: &mut *mut c_void) {
|
||||
let refs = Rc::from_raw(*user_data as *mut RefCell<SDL2Context>);
|
||||
|
||||
{
|
||||
let refs = &mut *refs.as_ptr();
|
||||
}
|
||||
|
||||
fn swap_buffers(&self) {
|
||||
let mut refs = self.0.borrow();
|
||||
refs.window.window().gl_swap_window();
|
||||
}
|
||||
|
||||
*user_data = Rc::into_raw(refs) as *mut c_void;
|
||||
fn set_swap_mode(&self, mode: SwapMode) {
|
||||
match mode {
|
||||
SwapMode::Immediate => unsafe {
|
||||
sdl2_sys::SDL_GL_SetSwapInterval(0);
|
||||
},
|
||||
SwapMode::VSync => unsafe {
|
||||
sdl2_sys::SDL_GL_SetSwapInterval(1);
|
||||
},
|
||||
SwapMode::Adaptive => unsafe {
|
||||
if sdl2_sys::SDL_GL_SetSwapInterval(-1) == -1 {
|
||||
log::warn!("Failed to enable variable refresh rate, falling back to non-V-Sync.");
|
||||
sdl2_sys::SDL_GL_SetSwapInterval(0);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let gl_context =
|
||||
GLContext { gles2_mode: false, is_sdl: true, get_proc_address, swap_buffers, user_data, ctx };
|
||||
let platform = Box::new(SDL2GLPlatform(self.refs.clone()));
|
||||
let gl_context: GLContext = GLContext { gles2_mode: false, platform, ctx };
|
||||
|
||||
return Ok(Box::new(OpenGLRenderer::new(gl_context, imgui)));
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
use crate::framework::backend::{init_backend, BackendRenderer};
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::filesystem::Filesystem;
|
||||
use crate::framework::gamepad::GamepadContext;
|
||||
use crate::framework::graphics::VSyncMode;
|
||||
use crate::framework::keyboard::KeyboardContext;
|
||||
use crate::game::Game;
|
||||
|
||||
use super::backend::{init_backend, BackendRenderer};
|
||||
use super::error::GameResult;
|
||||
use super::filesystem::Filesystem;
|
||||
use super::gamepad::GamepadContext;
|
||||
use super::graphics::SwapMode;
|
||||
use super::keyboard::KeyboardContext;
|
||||
|
||||
pub struct Context {
|
||||
pub headless: bool,
|
||||
pub shutdown_requested: bool,
|
||||
|
@ -18,7 +19,7 @@ pub struct Context {
|
|||
pub(crate) real_screen_size: (u32, u32),
|
||||
pub(crate) screen_size: (f32, f32),
|
||||
pub(crate) screen_insets: (f32, f32, f32, f32),
|
||||
pub(crate) vsync_mode: VSyncMode,
|
||||
pub(crate) swap_mode: SwapMode,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
|
@ -35,7 +36,7 @@ impl Context {
|
|||
real_screen_size: (320, 240),
|
||||
screen_size: (320.0, 240.0),
|
||||
screen_insets: (0.0, 0.0, 0.0, 0.0),
|
||||
vsync_mode: VSyncMode::Uncapped,
|
||||
swap_mode: SwapMode::VSync,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,18 +23,11 @@ pub enum BlendMode {
|
|||
Multiply,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||
pub enum VSyncMode {
|
||||
/// No V-Sync - uncapped frame rate
|
||||
Uncapped,
|
||||
/// Synchronized to V-Sync
|
||||
VSync,
|
||||
/// Variable Refresh Rate - Synchronized to game tick interval
|
||||
VRRTickSync1x,
|
||||
/// Variable Refresh Rate - Synchronized to 2 * game tick interval
|
||||
VRRTickSync2x,
|
||||
/// Variable Refresh Rate - Synchronized to 3 * game tick interval
|
||||
VRRTickSync3x,
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum SwapMode {
|
||||
Immediate = 0,
|
||||
VSync = 1,
|
||||
Adaptive = -1,
|
||||
}
|
||||
|
||||
pub fn clear(ctx: &mut Context, color: Color) {
|
||||
|
@ -51,10 +44,12 @@ pub fn present(ctx: &mut Context) -> GameResult {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_vsync_mode(ctx: &mut Context, mode: VSyncMode) -> GameResult {
|
||||
pub fn set_swap_mode(ctx: &mut Context, mode: SwapMode) -> GameResult {
|
||||
if let Some(renderer) = &mut ctx.renderer {
|
||||
ctx.vsync_mode = mode;
|
||||
renderer.set_vsync_mode(mode);
|
||||
if ctx.swap_mode != mode {
|
||||
ctx.swap_mode = mode;
|
||||
renderer.set_swap_mode(mode);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -135,7 +130,6 @@ pub fn set_clip_rect(ctx: &mut Context, rect: Option<Rect>) -> GameResult {
|
|||
Err(GameError::RenderError("Rendering backend hasn't been initialized yet.".to_string()))
|
||||
}
|
||||
|
||||
|
||||
pub fn imgui_context(ctx: &Context) -> GameResult<&mut imgui::Context> {
|
||||
if let Some(renderer) = ctx.renderer.as_ref() {
|
||||
return renderer.imgui();
|
||||
|
|
|
@ -1,38 +1,40 @@
|
|||
use std::any::Any;
|
||||
use std::cell::{RefCell, UnsafeCell};
|
||||
use std::borrow::BorrowMut;
|
||||
use std::cell::{Cell, RefCell, UnsafeCell};
|
||||
use std::ffi::{c_void, CStr};
|
||||
use std::hint::unreachable_unchecked;
|
||||
use std::mem;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ptr::null;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use imgui::{DrawCmd, DrawCmdParams, DrawData, DrawIdx, DrawVert, TextureId, Ui};
|
||||
|
||||
use super::backend::{BackendRenderer, BackendShader, BackendTexture, SpriteBatchCommand, VertexData};
|
||||
use super::context::Context;
|
||||
use super::error::GameError;
|
||||
use super::error::GameError::RenderError;
|
||||
use super::error::GameResult;
|
||||
use super::gl;
|
||||
use super::gl::types::*;
|
||||
use super::graphics::BlendMode;
|
||||
use super::graphics::SwapMode;
|
||||
use super::util::{field_offset, return_param};
|
||||
use crate::common::{Color, Rect};
|
||||
use crate::framework::backend::{BackendRenderer, BackendShader, BackendTexture, SpriteBatchCommand, VertexData};
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameError;
|
||||
use crate::framework::error::GameError::RenderError;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::gl;
|
||||
use crate::framework::gl::types::*;
|
||||
use crate::framework::graphics::{BlendMode, VSyncMode};
|
||||
use crate::framework::util::{field_offset, return_param};
|
||||
use crate::game::GAME_SUSPENDED;
|
||||
|
||||
pub trait GLPlatformFunctions {
|
||||
fn get_proc_address(&self, name: &str) -> *const c_void;
|
||||
|
||||
fn swap_buffers(&self);
|
||||
|
||||
fn set_swap_mode(&self, mode: SwapMode);
|
||||
}
|
||||
|
||||
pub struct GLContext {
|
||||
pub gles2_mode: bool,
|
||||
pub is_sdl: bool,
|
||||
pub get_proc_address: unsafe fn(user_data: &mut *mut c_void, name: &str) -> *const c_void,
|
||||
pub swap_buffers: unsafe fn(user_data: &mut *mut c_void),
|
||||
pub user_data: *mut c_void,
|
||||
pub platform: Box<dyn GLPlatformFunctions>,
|
||||
pub ctx: *mut Context,
|
||||
}
|
||||
|
||||
|
@ -44,7 +46,7 @@ pub struct OpenGLTexture {
|
|||
shader: RenderShader,
|
||||
vbo: GLuint,
|
||||
vertices: Vec<VertexData>,
|
||||
context_active: Arc<RefCell<bool>>,
|
||||
gl: Rc<Gl>,
|
||||
}
|
||||
|
||||
impl BackendTexture for OpenGLTexture {
|
||||
|
@ -224,38 +226,35 @@ impl BackendTexture for OpenGLTexture {
|
|||
|
||||
fn draw(&mut self) -> GameResult {
|
||||
unsafe {
|
||||
if let Some(gl) = &GL_PROC {
|
||||
if self.texture_id == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if gl.gl.BindSampler.is_loaded() {
|
||||
gl.gl.BindSampler(0, 0);
|
||||
}
|
||||
|
||||
gl.gl.Enable(gl::TEXTURE_2D);
|
||||
gl.gl.Enable(gl::BLEND);
|
||||
gl.gl.Disable(gl::DEPTH_TEST);
|
||||
|
||||
self.shader.bind_attrib_pointer(gl, self.vbo);
|
||||
|
||||
gl.gl.BindTexture(gl::TEXTURE_2D, self.texture_id);
|
||||
gl.gl.BufferData(
|
||||
gl::ARRAY_BUFFER,
|
||||
(self.vertices.len() * mem::size_of::<VertexData>()) as _,
|
||||
self.vertices.as_ptr() as _,
|
||||
gl::STREAM_DRAW,
|
||||
);
|
||||
|
||||
gl.gl.DrawArrays(gl::TRIANGLES, 0, self.vertices.len() as _);
|
||||
|
||||
gl.gl.BindTexture(gl::TEXTURE_2D, 0);
|
||||
gl.gl.BindBuffer(gl::ARRAY_BUFFER, 0);
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(RenderError("No OpenGL context available!".to_string()))
|
||||
let gl = self.gl.as_ref();
|
||||
if self.texture_id == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if gl.gl.BindSampler.is_loaded() {
|
||||
gl.gl.BindSampler(0, 0);
|
||||
}
|
||||
|
||||
gl.gl.Enable(gl::TEXTURE_2D);
|
||||
gl.gl.Enable(gl::BLEND);
|
||||
gl.gl.Disable(gl::DEPTH_TEST);
|
||||
|
||||
self.shader.bind_attrib_pointer(gl, self.vbo);
|
||||
|
||||
gl.gl.BindTexture(gl::TEXTURE_2D, self.texture_id);
|
||||
gl.gl.BufferData(
|
||||
gl::ARRAY_BUFFER,
|
||||
(self.vertices.len() * mem::size_of::<VertexData>()) as _,
|
||||
self.vertices.as_ptr() as _,
|
||||
gl::STREAM_DRAW,
|
||||
);
|
||||
|
||||
gl.gl.DrawArrays(gl::TRIANGLES, 0, self.vertices.len() as _);
|
||||
|
||||
gl.gl.BindTexture(gl::TEXTURE_2D, 0);
|
||||
gl.gl.BindBuffer(gl::ARRAY_BUFFER, 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,16 +265,20 @@ impl BackendTexture for OpenGLTexture {
|
|||
|
||||
impl Drop for OpenGLTexture {
|
||||
fn drop(&mut self) {
|
||||
if *self.context_active.as_ref().borrow() {
|
||||
unsafe {
|
||||
if let Some(gl) = &GL_PROC {
|
||||
if self.texture_id != 0 {
|
||||
let texture_id = &self.texture_id;
|
||||
gl.gl.DeleteTextures(1, texture_id as *const _);
|
||||
}
|
||||
unsafe {
|
||||
let gl = self.gl.as_ref();
|
||||
if !*gl.context_active.borrow() {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.framebuffer_id != 0 {}
|
||||
}
|
||||
if self.texture_id != 0 {
|
||||
let texture_id = &self.texture_id;
|
||||
gl.gl.DeleteTextures(1, texture_id as *const _);
|
||||
}
|
||||
|
||||
if self.framebuffer_id != 0 {
|
||||
let framebuffer_id = &self.framebuffer_id;
|
||||
gl.gl.DeleteFramebuffers(1, framebuffer_id as *const _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -492,21 +495,18 @@ impl RenderData {
|
|||
// iOS has "unusual" framebuffer setup, where we can't rely on 0 as the system provided render target.
|
||||
self.render_fbo = return_param(|x| gl.gl.GetIntegerv(gl::FRAMEBUFFER_BINDING, x));
|
||||
|
||||
self.tex_shader =
|
||||
RenderShader::compile(gl, vshdr_basic, fshdr_tex).unwrap_or_else(|e| {
|
||||
log::error!("Failed to compile texture shader: {}", e);
|
||||
RenderShader::default()
|
||||
});
|
||||
self.fill_shader =
|
||||
RenderShader::compile(gl, vshdr_basic, fshdr_fill).unwrap_or_else(|e| {
|
||||
log::error!("Failed to compile fill shader: {}", e);
|
||||
RenderShader::default()
|
||||
});
|
||||
self.fill_water_shader =
|
||||
RenderShader::compile(gl, vshdr_basic, fshdr_fill_water).unwrap_or_else(|e| {
|
||||
log::error!("Failed to compile fill water shader: {}", e);
|
||||
RenderShader::default()
|
||||
});
|
||||
self.tex_shader = RenderShader::compile(gl, vshdr_basic, fshdr_tex).unwrap_or_else(|e| {
|
||||
log::error!("Failed to compile texture shader: {}", e);
|
||||
RenderShader::default()
|
||||
});
|
||||
self.fill_shader = RenderShader::compile(gl, vshdr_basic, fshdr_fill).unwrap_or_else(|e| {
|
||||
log::error!("Failed to compile fill shader: {}", e);
|
||||
RenderShader::default()
|
||||
});
|
||||
self.fill_water_shader = RenderShader::compile(gl, vshdr_basic, fshdr_fill_water).unwrap_or_else(|e| {
|
||||
log::error!("Failed to compile fill water shader: {}", e);
|
||||
RenderShader::default()
|
||||
});
|
||||
|
||||
self.vbo = return_param(|x| gl.gl.GenBuffers(1, x));
|
||||
self.ebo = return_param(|x| gl.gl.GenBuffers(1, x));
|
||||
|
@ -575,17 +575,12 @@ impl RenderData {
|
|||
|
||||
pub struct Gl {
|
||||
pub gl: gl::Gles2,
|
||||
pub context_active: RefCell<bool>,
|
||||
}
|
||||
|
||||
static mut GL_PROC: Option<Gl> = None;
|
||||
|
||||
pub fn load_gl(gl_context: &mut GLContext) -> &'static Gl {
|
||||
pub fn load_gl(gl_context: &mut GLContext) -> Gl {
|
||||
unsafe {
|
||||
if let Some(gl) = &GL_PROC {
|
||||
return gl;
|
||||
}
|
||||
|
||||
let gl = gl::Gles2::load_with(|ptr| (gl_context.get_proc_address)(&mut gl_context.user_data, ptr));
|
||||
let gl = gl::Gles2::load_with(|ptr| gl_context.platform.get_proc_address(ptr));
|
||||
|
||||
let version = {
|
||||
let p = gl.GetString(gl::VERSION);
|
||||
|
@ -599,16 +594,15 @@ pub fn load_gl(gl_context: &mut GLContext) -> &'static Gl {
|
|||
|
||||
log::info!("OpenGL version {}", version);
|
||||
|
||||
GL_PROC = Some(Gl { gl });
|
||||
GL_PROC.as_ref().unwrap()
|
||||
Gl { gl, context_active: RefCell::new(true) }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OpenGLRenderer {
|
||||
refs: GLContext,
|
||||
gl: Option<Rc<Gl>>,
|
||||
imgui: UnsafeCell<imgui::Context>,
|
||||
render_data: RenderData,
|
||||
context_active: Arc<RefCell<bool>>,
|
||||
def_matrix: [[f32; 4]; 4],
|
||||
curr_matrix: [[f32; 4]; 4],
|
||||
}
|
||||
|
@ -617,22 +611,25 @@ impl OpenGLRenderer {
|
|||
pub fn new(refs: GLContext, imgui: imgui::Context) -> OpenGLRenderer {
|
||||
OpenGLRenderer {
|
||||
refs,
|
||||
gl: None,
|
||||
imgui: UnsafeCell::new(imgui),
|
||||
render_data: RenderData::new(),
|
||||
context_active: Arc::new(RefCell::new(true)),
|
||||
def_matrix: [[0.0; 4]; 4],
|
||||
curr_matrix: [[0.0; 4]; 4],
|
||||
}
|
||||
}
|
||||
|
||||
fn get_context(&mut self) -> Option<(&mut GLContext, &'static Gl)> {
|
||||
fn get_context(&mut self) -> Option<(&mut GLContext, Rc<Gl>)> {
|
||||
let imgui = unsafe { &mut *self.imgui.get() };
|
||||
|
||||
let gles2 = self.refs.gles2_mode;
|
||||
let gl = load_gl(&mut self.refs);
|
||||
if let None = self.gl {
|
||||
self.gl = Some(Rc::new(load_gl(&mut self.refs)));
|
||||
}
|
||||
let gl = self.gl.clone().unwrap();
|
||||
|
||||
if !self.render_data.initialized {
|
||||
self.render_data.init(gles2, imgui, gl);
|
||||
self.render_data.init(gles2, imgui, &gl);
|
||||
}
|
||||
|
||||
Some((&mut self.refs, gl))
|
||||
|
@ -674,7 +671,7 @@ impl BackendRenderer for OpenGLRenderer {
|
|||
let matrix =
|
||||
[[2.0f32, 0.0, 0.0, 0.0], [0.0, -2.0, 0.0, 0.0], [0.0, 0.0, -1.0, 0.0], [-1.0, 1.0, 0.0, 1.0]];
|
||||
|
||||
self.render_data.tex_shader.bind_attrib_pointer(gl, self.render_data.vbo);
|
||||
self.render_data.tex_shader.bind_attrib_pointer(&gl, self.render_data.vbo);
|
||||
gl.gl.UniformMatrix4fv(self.render_data.tex_shader.proj_mtx, 1, gl::FALSE, matrix.as_ptr() as _);
|
||||
|
||||
let color = (255, 255, 255, 255);
|
||||
|
@ -693,46 +690,24 @@ impl BackendRenderer for OpenGLRenderer {
|
|||
self.render_data.surf_texture,
|
||||
BackendShader::Texture,
|
||||
)?;
|
||||
|
||||
//gl.gl.Finish();
|
||||
}
|
||||
|
||||
if let Some((context, _)) = self.get_context() {
|
||||
(context.swap_buffers)(&mut context.user_data);
|
||||
context.platform.swap_buffers();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vsync_mode(&mut self, mode: VSyncMode) -> GameResult {
|
||||
if !self.refs.is_sdl {
|
||||
return Ok(());
|
||||
fn set_swap_mode(&mut self, mode: SwapMode) -> GameResult {
|
||||
if let Some((ctx, _)) = self.get_context() {
|
||||
ctx.platform.set_swap_mode(mode);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(RenderError("No OpenGL context available!".to_string()))
|
||||
}
|
||||
|
||||
#[cfg(feature = "backend-sdl")]
|
||||
unsafe {
|
||||
let ctx = &mut *self.refs.ctx;
|
||||
|
||||
match mode {
|
||||
VSyncMode::Uncapped => {
|
||||
sdl2_sys::SDL_GL_SetSwapInterval(0);
|
||||
}
|
||||
VSyncMode::VSync => {
|
||||
sdl2_sys::SDL_GL_SetSwapInterval(1);
|
||||
}
|
||||
_ => {
|
||||
if sdl2_sys::SDL_GL_SetSwapInterval(-1) == -1 {
|
||||
log::warn!("Failed to enable variable refresh rate, falling back to non-V-Sync.");
|
||||
sdl2_sys::SDL_GL_SetSwapInterval(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prepare_draw(&mut self, width: f32, height: f32) -> GameResult {
|
||||
if let Some((_, gl)) = self.get_context() {
|
||||
unsafe {
|
||||
|
@ -854,7 +829,7 @@ impl BackendRenderer for OpenGLRenderer {
|
|||
vertices: Vec::new(),
|
||||
shader: self.render_data.tex_shader,
|
||||
vbo: self.render_data.vbo,
|
||||
context_active: self.context_active.clone(),
|
||||
gl: gl.clone(),
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
|
@ -893,7 +868,7 @@ impl BackendRenderer for OpenGLRenderer {
|
|||
vertices: Vec::new(),
|
||||
shader: self.render_data.tex_shader,
|
||||
vbo: self.render_data.vbo,
|
||||
context_active: self.context_active.clone(),
|
||||
gl: gl.clone(),
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
|
@ -1009,7 +984,7 @@ impl BackendRenderer for OpenGLRenderer {
|
|||
|
||||
fn draw_rect(&mut self, rect: Rect<isize>, color: Color) -> GameResult {
|
||||
unsafe {
|
||||
if let Some(gl) = &GL_PROC {
|
||||
if let Some((_, gl)) = self.get_context() {
|
||||
let color = color.to_rgba();
|
||||
let mut uv = self.render_data.font_tex_size;
|
||||
uv.0 = 0.0 / uv.0;
|
||||
|
@ -1024,7 +999,7 @@ impl BackendRenderer for OpenGLRenderer {
|
|||
VertexData { position: (rect.right as _, rect.bottom as _), uv, color },
|
||||
];
|
||||
|
||||
self.render_data.fill_shader.bind_attrib_pointer(gl, self.render_data.vbo);
|
||||
self.render_data.fill_shader.bind_attrib_pointer(&gl, self.render_data.vbo);
|
||||
|
||||
gl.gl.BindTexture(gl::TEXTURE_2D, self.render_data.font_texture);
|
||||
gl.gl.BindBuffer(gl::ARRAY_BUFFER, self.render_data.vbo);
|
||||
|
@ -1262,16 +1237,16 @@ impl OpenGLRenderer {
|
|||
mut texture: u32,
|
||||
shader: BackendShader,
|
||||
) -> GameResult<()> {
|
||||
if let Some(gl) = &GL_PROC {
|
||||
if let Some((_, gl)) = self.get_context() {
|
||||
match shader {
|
||||
BackendShader::Fill => {
|
||||
self.render_data.fill_shader.bind_attrib_pointer(gl, self.render_data.vbo)?;
|
||||
self.render_data.fill_shader.bind_attrib_pointer(&gl, self.render_data.vbo)?;
|
||||
}
|
||||
BackendShader::Texture => {
|
||||
self.render_data.tex_shader.bind_attrib_pointer(gl, self.render_data.vbo)?;
|
||||
self.render_data.tex_shader.bind_attrib_pointer(&gl, self.render_data.vbo)?;
|
||||
}
|
||||
BackendShader::WaterFill(scale, t, frame_pos) => {
|
||||
self.render_data.fill_water_shader.bind_attrib_pointer(gl, self.render_data.vbo)?;
|
||||
self.render_data.fill_water_shader.bind_attrib_pointer(&gl, self.render_data.vbo)?;
|
||||
gl.gl.Uniform1f(self.render_data.fill_water_shader.scale, scale);
|
||||
gl.gl.Uniform1f(self.render_data.fill_water_shader.time, t);
|
||||
gl.gl.Uniform2f(self.render_data.fill_water_shader.frame_offset, frame_pos.0, frame_pos.1);
|
||||
|
@ -1301,6 +1276,8 @@ impl OpenGLRenderer {
|
|||
|
||||
impl Drop for OpenGLRenderer {
|
||||
fn drop(&mut self) {
|
||||
*self.context_active.as_ref().borrow_mut() = false;
|
||||
if let Some(gl) = &self.gl {
|
||||
gl.context_active.replace(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,10 +11,10 @@ use scripting::tsc::text_script::ScriptMode;
|
|||
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics;
|
||||
use crate::framework::graphics::VSyncMode;
|
||||
use crate::framework::graphics::{self, SwapMode};
|
||||
use crate::framework::ui::UI;
|
||||
use crate::game::filesystem_container::FilesystemContainer;
|
||||
use crate::game::settings::VSyncMode;
|
||||
use crate::game::shared_game_state::{Fps, SharedGameState, TimingMode};
|
||||
use crate::graphics::texture_set::{G_MAG, I_MAG};
|
||||
use crate::scene::loading_scene::LoadingScene;
|
||||
|
@ -61,7 +61,7 @@ pub struct Game {
|
|||
impl Game {
|
||||
fn new(ctx: &mut Context) -> GameResult<Game> {
|
||||
let s = Game {
|
||||
scene: RefCell::new( None),
|
||||
scene: RefCell::new(None),
|
||||
ui: UI::new(ctx)?,
|
||||
state: RefCell::new(SharedGameState::new(ctx)?),
|
||||
start_time: Instant::now(),
|
||||
|
@ -80,14 +80,12 @@ impl Game {
|
|||
let state_ref = self.state.get_mut();
|
||||
|
||||
if let Some(scene) = self.scene.get_mut() {
|
||||
|
||||
let speed = if state_ref.textscript_vm.mode == ScriptMode::Map
|
||||
&& state_ref.textscript_vm.flags.cutscene_skip()
|
||||
{
|
||||
4.0
|
||||
} else {
|
||||
1.0
|
||||
} * state_ref.settings.speed;
|
||||
let speed =
|
||||
if state_ref.textscript_vm.mode == ScriptMode::Map && state_ref.textscript_vm.flags.cutscene_skip() {
|
||||
4.0
|
||||
} else {
|
||||
1.0
|
||||
} * state_ref.settings.speed;
|
||||
|
||||
match state_ref.settings.timing_mode {
|
||||
TimingMode::_50Hz | TimingMode::_60Hz => {
|
||||
|
@ -133,7 +131,7 @@ impl Game {
|
|||
if let Some(mut next_scene) = next_scene {
|
||||
next_scene.init(state_ref, ctx)?;
|
||||
*self.scene.get_mut() = Some(next_scene);
|
||||
|
||||
|
||||
self.loops = 0;
|
||||
state_ref.frame_time = 0.0;
|
||||
}
|
||||
|
@ -142,14 +140,21 @@ impl Game {
|
|||
}
|
||||
|
||||
pub(crate) fn draw(&mut self, ctx: &mut Context) -> GameResult {
|
||||
match ctx.vsync_mode {
|
||||
VSyncMode::Uncapped | VSyncMode::VSync => {
|
||||
let vsync_mode = self.state.get_mut().settings.vsync_mode;
|
||||
match vsync_mode {
|
||||
VSyncMode::Uncapped => {
|
||||
graphics::set_swap_mode(ctx, SwapMode::Immediate)?;
|
||||
self.present = true;
|
||||
}
|
||||
VSyncMode::VSync => {
|
||||
graphics::set_swap_mode(ctx, SwapMode::VSync)?;
|
||||
self.present = true;
|
||||
}
|
||||
_ => unsafe {
|
||||
graphics::set_swap_mode(ctx, SwapMode::Adaptive)?;
|
||||
self.present = false;
|
||||
|
||||
let divisor = match ctx.vsync_mode {
|
||||
let divisor = match vsync_mode {
|
||||
VSyncMode::VRRTickSync1x => 1,
|
||||
VSyncMode::VRRTickSync2x => 2,
|
||||
VSyncMode::VRRTickSync3x => 3,
|
||||
|
@ -194,7 +199,8 @@ impl Game {
|
|||
|
||||
let n1 = (elapsed - self.last_tick) as f64;
|
||||
let n2 = (self.next_tick - self.last_tick) as f64;
|
||||
self.state.get_mut().frame_time = if self.state.get_mut().settings.motion_interpolation { n1 / n2 } else { 1.0 };
|
||||
self.state.get_mut().frame_time =
|
||||
if self.state.get_mut().settings.motion_interpolation { n1 / n2 } else { 1.0 };
|
||||
}
|
||||
unsafe {
|
||||
G_MAG = if self.state.get_mut().settings.subpixel_coords { self.state.get_mut().scale } else { 1.0 };
|
||||
|
|
|
@ -4,7 +4,6 @@ use crate::framework::context::Context;
|
|||
use crate::framework::error::GameResult;
|
||||
use crate::framework::filesystem::{user_create, user_open};
|
||||
use crate::framework::gamepad::{Axis, AxisDirection, Button, PlayerControllerInputType};
|
||||
use crate::framework::graphics::VSyncMode;
|
||||
use crate::framework::keyboard::ScanCode;
|
||||
use crate::game::player::TargetPlayer;
|
||||
use crate::game::shared_game_state::{CutsceneSkipMode, ScreenShakeIntensity, TimingMode, WindowMode};
|
||||
|
@ -582,6 +581,20 @@ pub fn default_controller_axis_sensitivity() -> f64 {
|
|||
0.3
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||
pub enum VSyncMode {
|
||||
/// No V-Sync - uncapped frame rate
|
||||
Uncapped,
|
||||
/// Synchronized to V-Sync
|
||||
VSync,
|
||||
/// Variable Refresh Rate - Synchronized to game tick interval
|
||||
VRRTickSync1x,
|
||||
/// Variable Refresh Rate - Synchronized to 2 * game tick interval
|
||||
VRRTickSync2x,
|
||||
/// Variable Refresh Rate - Synchronized to 3 * game tick interval
|
||||
VRRTickSync3x,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum LightingEngine {
|
||||
Default,
|
||||
|
|
|
@ -2,9 +2,9 @@ use itertools::Itertools;
|
|||
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics::VSyncMode;
|
||||
use crate::framework::{filesystem, graphics};
|
||||
use crate::framework::filesystem;
|
||||
use crate::game::settings::LightingEngine;
|
||||
use crate::game::settings::VSyncMode;
|
||||
use crate::game::shared_game_state::{CutsceneSkipMode, ScreenShakeIntensity, SharedGameState, TimingMode, WindowMode};
|
||||
use crate::graphics::font::Font;
|
||||
use crate::input::combined_menu_controller::CombinedMenuController;
|
||||
|
@ -725,7 +725,6 @@ impl SettingsMenu {
|
|||
|
||||
*value = new_value;
|
||||
state.settings.vsync_mode = new_mode;
|
||||
graphics::set_vsync_mode(ctx, new_mode)?;
|
||||
|
||||
let _ = state.settings.save(ctx);
|
||||
}
|
||||
|
@ -742,7 +741,6 @@ impl SettingsMenu {
|
|||
|
||||
*value = new_value;
|
||||
state.settings.vsync_mode = new_mode;
|
||||
graphics::set_vsync_mode(ctx, new_mode)?;
|
||||
|
||||
let _ = state.settings.save(ctx);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics;
|
||||
use crate::game::shared_game_state::SharedGameState;
|
||||
use crate::scene::no_data_scene::NoDataScene;
|
||||
use crate::scene::Scene;
|
||||
|
@ -44,8 +43,6 @@ impl Scene for LoadingScene {
|
|||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
graphics::set_vsync_mode(ctx, state.settings.vsync_mode)?;
|
||||
|
||||
match state.texture_set.get_or_load_batch(ctx, &state.constants, "Loading") {
|
||||
Ok(batch) => {
|
||||
batch.add(
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use std::{
|
||||
cell::UnsafeCell,
|
||||
sync::{Mutex, OnceLock},
|
||||
cell::UnsafeCell, fmt::{self, Debug, Display, Formatter}, hash::{Hash, Hasher}, sync::{Mutex, OnceLock}
|
||||
};
|
||||
|
||||
const BUCKET_BITS: usize = 10;
|
||||
|
@ -52,7 +51,9 @@ const fn string_hash(data: &str) -> HashType {
|
|||
let mut i = 0;
|
||||
let bytes = data.as_bytes();
|
||||
while i < bytes.len() {
|
||||
let byte = bytes[i] as u32;
|
||||
// let byte = bytes[i] as u32;
|
||||
// this is safe because we are guaranteed to have a valid index, avoids redundant bounds check
|
||||
let byte: u32 = unsafe { bytes.as_ptr().add(i).read() } as u32;
|
||||
hash ^= byte;
|
||||
hash = hash.wrapping_mul(0x01000193u32);
|
||||
i += 1;
|
||||
|
@ -69,11 +70,11 @@ impl AtomStore {
|
|||
fn find(&self, data: &str) -> Option<Atom> {
|
||||
let hash = string_hash(data);
|
||||
let bucket = (hash as usize) & BUCKET_MASK;
|
||||
debug_assert!(bucket < BUCKET_SIZE); // if this ever fails, the below is UB
|
||||
let bucket = unsafe { self.buckets.get_unchecked(bucket) };
|
||||
|
||||
if let Some(atom) = bucket {
|
||||
let iter = atom.iter();
|
||||
for atom in iter {
|
||||
for atom in atom.iter() {
|
||||
if atom.data == data {
|
||||
return Some(Atom(atom));
|
||||
}
|
||||
|
@ -95,12 +96,13 @@ impl AtomStore {
|
|||
fn insert(&mut self, data: &'static str) -> Atom {
|
||||
let hash = string_hash(data);
|
||||
let bucket = (hash as usize) & BUCKET_MASK;
|
||||
|
||||
debug_assert!(bucket < BUCKET_SIZE); // if this ever fails, the below is UB
|
||||
let bucket = unsafe { self.buckets.get_unchecked_mut(bucket) };
|
||||
|
||||
if let Some(atom) = bucket {
|
||||
let iter = atom.iter();
|
||||
let mut prev_atom = *atom;
|
||||
for atom in iter {
|
||||
for atom in atom.iter() {
|
||||
if atom.data == data {
|
||||
return Atom(atom);
|
||||
}
|
||||
|
@ -112,6 +114,7 @@ impl AtomStore {
|
|||
unsafe {
|
||||
*prev_atom.next.get() = Some(new_atom);
|
||||
}
|
||||
|
||||
Atom(new_atom)
|
||||
} else {
|
||||
let atom = AtomData::new(data, hash);
|
||||
|
@ -119,42 +122,24 @@ impl AtomStore {
|
|||
Atom(atom)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, debug_assertions))]
|
||||
fn print_debug(&self) {
|
||||
for (i, bucket) in self.buckets.iter().enumerate() {
|
||||
if let Some(atom) = bucket {
|
||||
println!("Bucket {}: {:?}", i, atom.data);
|
||||
let iter = atom.iter();
|
||||
for atom in iter {
|
||||
println!(" {:?}", atom.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for AtomStore {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
impl Debug for AtomStore {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.write_str("AtomStore")?;
|
||||
|
||||
#[cfg(any(test, debug_assertions))]
|
||||
{
|
||||
struct AtomPrintList(&'static AtomData);
|
||||
impl Debug for AtomPrintList {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.debug_list().entries(self.0.iter().map(|atom| atom.data)).finish()
|
||||
}
|
||||
}
|
||||
|
||||
let mut dbg = f.debug_map();
|
||||
for (i, bucket) in self.buckets.iter().enumerate() {
|
||||
if let Some(atom) = bucket {
|
||||
struct AtomPrintList(&'static AtomData);
|
||||
impl std::fmt::Debug for AtomPrintList {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let mut list = f.debug_list();
|
||||
let iter = self.0.iter();
|
||||
for atom in iter {
|
||||
list.entry(&atom.data);
|
||||
}
|
||||
list.finish()
|
||||
}
|
||||
}
|
||||
|
||||
dbg.entry(&i, &AtomPrintList(atom));
|
||||
}
|
||||
}
|
||||
|
@ -163,6 +148,7 @@ impl std::fmt::Debug for AtomStore {
|
|||
}
|
||||
}
|
||||
|
||||
// todo: Rust 1.80 has LazyLock, which at time of writing is stable, but we'll wait a bit for everyone to catch up
|
||||
static ATOM_STORE: OnceLock<Mutex<Box<AtomStore>>> = OnceLock::new();
|
||||
|
||||
fn get_atom_store() -> &'static Mutex<Box<AtomStore>> {
|
||||
|
@ -199,23 +185,24 @@ impl PartialEq for Atom {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::hash::Hash for Atom {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
unsafe { (*self.0).hash.hash(state) }
|
||||
impl Hash for Atom {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.data().hash.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Atom {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
impl Debug for Atom {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
|
||||
f.write_str("Atom(")?;
|
||||
self.data().data.fmt(f)?;
|
||||
Debug::fmt(self.data().data, f)?;
|
||||
f.write_str(")")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Atom {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
self.data().data.fmt(f)
|
||||
impl Display for Atom {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
Display::fmt(self.data().data, f)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
22
src/util/expression/ast.rs
Normal file
22
src/util/expression/ast.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum BinaryOp {
|
||||
Add,
|
||||
Subtract,
|
||||
Multiply,
|
||||
Divide,
|
||||
Modulus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum UnaryOp {
|
||||
Negate,
|
||||
Not,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Node {
|
||||
Float(f64),
|
||||
Integer(i64),
|
||||
BinaryExpr { left: Box<Node>, op: BinaryOp, right: Box<Node> },
|
||||
UnaryExpr { op: UnaryOp, expr: Box<Node> },
|
||||
}
|
266
src/util/expression/lexer.rs
Normal file
266
src/util/expression/lexer.rs
Normal file
|
@ -0,0 +1,266 @@
|
|||
use std::{
|
||||
fmt::{Debug, Display, Formatter},
|
||||
iter::Peekable,
|
||||
str::Chars,
|
||||
};
|
||||
|
||||
use super::token::{ArithmeticOp, SourceLocation, Token};
|
||||
|
||||
pub(super) struct Cursor<'a> {
|
||||
input: &'a str,
|
||||
iter: Peekable<Chars<'a>>,
|
||||
/// Character position in the input string
|
||||
pos: usize,
|
||||
location: SourceLocation,
|
||||
}
|
||||
|
||||
impl<'a> Cursor<'a> {
|
||||
pub fn new(input: &str) -> Cursor {
|
||||
Cursor { input, iter: input.chars().peekable(), pos: 0, location: SourceLocation { line: 1, column: 0 } }
|
||||
}
|
||||
|
||||
pub fn pos(&self) -> usize {
|
||||
self.pos
|
||||
}
|
||||
|
||||
fn inc_pos(&mut self) {
|
||||
let c = self.iter.peek();
|
||||
if c == Some(&'\n') {
|
||||
self.location.line += 1;
|
||||
self.location.column = 0;
|
||||
} else {
|
||||
self.location.column += 1;
|
||||
}
|
||||
self.pos += 1;
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> Option<char> {
|
||||
let c = self.iter.next()?;
|
||||
self.inc_pos();
|
||||
Some(c)
|
||||
}
|
||||
|
||||
pub fn next_if(&mut self, func: impl FnOnce(&char) -> bool) -> Option<char> {
|
||||
let c = self.iter.peek()?;
|
||||
if func(c) {
|
||||
self.next()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peek(&mut self) -> Option<char> {
|
||||
self.iter.peek().copied()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct Tokenizer<'a> {
|
||||
cursor: Cursor<'a>,
|
||||
pub tokens: Vec<(Token, SourceLocation)>,
|
||||
}
|
||||
|
||||
impl Tokenizer<'_> {
|
||||
pub fn new(input: &str) -> Tokenizer {
|
||||
Tokenizer { cursor: Cursor::new(input), tokens: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn scan(&mut self) -> LexResult {
|
||||
while let Some(c) = self.cursor.peek() {
|
||||
self.scan_item(c)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn next(&mut self) -> LexResult<char> {
|
||||
self.cursor.next().ok_or_else(|| LexError::UnexpectedEndOfInput)
|
||||
}
|
||||
|
||||
fn next_if(&mut self, func: impl FnOnce(&char) -> bool) -> LexResult<char> {
|
||||
self.cursor.next_if(func).ok_or_else(|| LexError::UnexpectedEndOfInput)
|
||||
}
|
||||
|
||||
fn scan_item(&mut self, c: char) -> LexResult {
|
||||
match c {
|
||||
' ' | '\t' | '\n' => {
|
||||
self.cursor.next();
|
||||
return Ok(());
|
||||
}
|
||||
'0'..='9' => self.number(),
|
||||
'+' | '-' | '*' | '/' | '%' => self.operator(),
|
||||
'(' | ')' => self.bracket(),
|
||||
_ => return self.unexpected(c),
|
||||
}
|
||||
}
|
||||
|
||||
fn number(&mut self) -> LexResult {
|
||||
let mut value = String::new();
|
||||
let mut is_fractional = false;
|
||||
let loc = self.cursor.location;
|
||||
|
||||
while let Ok(c) = self.next_if(|c| matches!(c, '0'..='9' | '.')) {
|
||||
if c == '.' {
|
||||
if is_fractional {
|
||||
return self.unexpected(c);
|
||||
}
|
||||
is_fractional = true;
|
||||
}
|
||||
|
||||
value.push(c);
|
||||
}
|
||||
|
||||
self.tokens.push((Token::Number { value, is_fractional }, loc));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn operator(&mut self) -> LexResult {
|
||||
let loc = self.cursor.location;
|
||||
let c = self.next()?;
|
||||
match c {
|
||||
'+' => self.tokens.push((Token::ArithmeticOp(ArithmeticOp::Add), loc)),
|
||||
'-' => self.tokens.push((Token::ArithmeticOp(ArithmeticOp::Subtract), loc)),
|
||||
'*' => self.tokens.push((Token::ArithmeticOp(ArithmeticOp::Multiply), loc)),
|
||||
'/' => self.tokens.push((Token::ArithmeticOp(ArithmeticOp::Divide), loc)),
|
||||
'%' => self.tokens.push((Token::ArithmeticOp(ArithmeticOp::Modulus), loc)),
|
||||
_ => return self.unexpected(c),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bracket(&mut self) -> LexResult {
|
||||
let loc = self.cursor.location;
|
||||
let c = self.next()?;
|
||||
match c {
|
||||
'(' => self.tokens.push((Token::LeftParen, loc)),
|
||||
')' => self.tokens.push((Token::RightParen, loc)),
|
||||
_ => return self.unexpected(c),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unexpected(&self, c: char) -> LexResult {
|
||||
let loc = self.cursor.location;
|
||||
Err(LexError::UnexpectedChar { c, loc })
|
||||
}
|
||||
}
|
||||
|
||||
pub type LexResult<T = ()> = Result<T, LexError>;
|
||||
|
||||
pub enum LexError {
|
||||
UnexpectedChar { c: char, loc: SourceLocation },
|
||||
UnexpectedEndOfInput,
|
||||
}
|
||||
|
||||
impl Display for LexError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
LexError::UnexpectedChar { c, loc } => {
|
||||
write!(f, "Unexpected character '{}' at {}", c, loc)
|
||||
}
|
||||
LexError::UnexpectedEndOfInput => write!(f, "Unexpected end of input"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for LexError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn test_expression(input: &str, result: Vec<Token>) {
|
||||
let mut tok = Tokenizer::new(input);
|
||||
tok.scan().unwrap();
|
||||
// println!("tokens: {:?}", tok.tokens);
|
||||
let tokens = tok.tokens.into_iter().map(|(t, _)| t).collect::<Vec<_>>();
|
||||
assert_eq!(tokens, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_number() {
|
||||
test_expression("1", vec![Token::Number { value: "1".to_string(), is_fractional: false }]);
|
||||
test_expression("1.0", vec![Token::Number { value: "1.0".to_string(), is_fractional: true }]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arithmetic_ops() {
|
||||
test_expression(
|
||||
"1 + 22 * 33 / 4444 + 55555",
|
||||
vec![
|
||||
Token::Number { value: "1".to_string(), is_fractional: false },
|
||||
Token::ArithmeticOp(ArithmeticOp::Add),
|
||||
Token::Number { value: "22".to_string(), is_fractional: false },
|
||||
Token::ArithmeticOp(ArithmeticOp::Multiply),
|
||||
Token::Number { value: "33".to_string(), is_fractional: false },
|
||||
Token::ArithmeticOp(ArithmeticOp::Divide),
|
||||
Token::Number { value: "4444".to_string(), is_fractional: false },
|
||||
Token::ArithmeticOp(ArithmeticOp::Add),
|
||||
Token::Number { value: "55555".to_string(), is_fractional: false },
|
||||
],
|
||||
);
|
||||
|
||||
test_expression(
|
||||
"1.0 + 2.0",
|
||||
vec![
|
||||
Token::Number { value: "1.0".to_string(), is_fractional: true },
|
||||
Token::ArithmeticOp(ArithmeticOp::Add),
|
||||
Token::Number { value: "2.0".to_string(), is_fractional: true },
|
||||
],
|
||||
);
|
||||
test_expression(
|
||||
"2.0 - 3",
|
||||
vec![
|
||||
Token::Number { value: "2.0".to_string(), is_fractional: true },
|
||||
Token::ArithmeticOp(ArithmeticOp::Subtract),
|
||||
Token::Number { value: "3".to_string(), is_fractional: false },
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_brackets() {
|
||||
test_expression(
|
||||
"(1 + 2)",
|
||||
vec![
|
||||
Token::LeftParen,
|
||||
Token::Number { value: "1".to_string(), is_fractional: false },
|
||||
Token::ArithmeticOp(ArithmeticOp::Add),
|
||||
Token::Number { value: "2".to_string(), is_fractional: false },
|
||||
Token::RightParen,
|
||||
],
|
||||
);
|
||||
test_expression(
|
||||
"2 * (3 + 4)",
|
||||
vec![
|
||||
Token::Number { value: "2".to_string(), is_fractional: false },
|
||||
Token::ArithmeticOp(ArithmeticOp::Multiply),
|
||||
Token::LeftParen,
|
||||
Token::Number { value: "3".to_string(), is_fractional: false },
|
||||
Token::ArithmeticOp(ArithmeticOp::Add),
|
||||
Token::Number { value: "4".to_string(), is_fractional: false },
|
||||
Token::RightParen,
|
||||
],
|
||||
);
|
||||
test_expression(
|
||||
"1 + ((2 * 3) + 4)",
|
||||
vec![
|
||||
Token::Number { value: "1".to_string(), is_fractional: false },
|
||||
Token::ArithmeticOp(ArithmeticOp::Add),
|
||||
Token::LeftParen,
|
||||
Token::LeftParen,
|
||||
Token::Number { value: "2".to_string(), is_fractional: false },
|
||||
Token::ArithmeticOp(ArithmeticOp::Multiply),
|
||||
Token::Number { value: "3".to_string(), is_fractional: false },
|
||||
Token::RightParen,
|
||||
Token::ArithmeticOp(ArithmeticOp::Add),
|
||||
Token::Number { value: "4".to_string(), is_fractional: false },
|
||||
Token::RightParen,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
5
src/util/expression/mod.rs
Normal file
5
src/util/expression/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
pub mod ast;
|
||||
pub mod lexer;
|
||||
pub mod parser;
|
||||
pub mod token;
|
||||
pub mod vm;
|
206
src/util/expression/parser.rs
Normal file
206
src/util/expression/parser.rs
Normal file
|
@ -0,0 +1,206 @@
|
|||
use std::fmt::{Debug, Display};
|
||||
|
||||
use super::{
|
||||
ast::{BinaryOp, Node, UnaryOp},
|
||||
token::{ArithmeticOp, SourceLocation, Token},
|
||||
};
|
||||
|
||||
pub struct Parser {
|
||||
ast: Vec<Node>,
|
||||
tokens: Vec<(Token, SourceLocation)>,
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
// -- the grammar --
|
||||
// expression -> term
|
||||
// term -> factor ( ('+' | '-') factor )*
|
||||
// factor -> unary ( ('*' | '/' | '%') unary )*
|
||||
// unary -> ('!' | '-') unary | primary
|
||||
// primary -> NUMBER | '(' expression ')'
|
||||
|
||||
impl Parser {
|
||||
pub fn new(tokens: Vec<(Token, SourceLocation)>) -> Parser {
|
||||
Parser { tokens, pos: 0, ast: Vec::new() }
|
||||
}
|
||||
|
||||
fn current(&self) -> ParseResult<&(Token, SourceLocation)> {
|
||||
self.tokens.get(self.pos).ok_or_else(|| ParseError::UnexpectedEnd)
|
||||
}
|
||||
|
||||
fn advance(&mut self) {
|
||||
self.pos += 1;
|
||||
}
|
||||
|
||||
fn is_end(&self) -> bool {
|
||||
self.pos >= self.tokens.len()
|
||||
}
|
||||
|
||||
fn expect(&self, token: Token) -> bool {
|
||||
!self.is_end() && self.current().map(|(t, _)| t == &token).unwrap_or(false)
|
||||
}
|
||||
|
||||
fn parse_expr(&mut self) -> ParseResult<Node> {
|
||||
self.parse_term()
|
||||
}
|
||||
|
||||
fn parse_term(&mut self) -> ParseResult<Node> {
|
||||
let mut node = self.parse_factor()?;
|
||||
|
||||
while !self.is_end() {
|
||||
let op = if let (Token::ArithmeticOp(op), _) = self.current()? {
|
||||
match op {
|
||||
ArithmeticOp::Add => BinaryOp::Add,
|
||||
ArithmeticOp::Subtract => BinaryOp::Subtract,
|
||||
_ => break,
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
};
|
||||
|
||||
self.advance();
|
||||
let right = self.parse_factor()?;
|
||||
node = Node::BinaryExpr { left: Box::new(node), op, right: Box::new(right) };
|
||||
}
|
||||
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
fn parse_factor(&mut self) -> ParseResult<Node> {
|
||||
let mut node = self.parse_unary()?;
|
||||
|
||||
while !self.is_end() {
|
||||
let op = if let (Token::ArithmeticOp(op), _) = self.current()? {
|
||||
match op {
|
||||
ArithmeticOp::Multiply => BinaryOp::Multiply,
|
||||
ArithmeticOp::Divide => BinaryOp::Divide,
|
||||
ArithmeticOp::Modulus => BinaryOp::Modulus,
|
||||
_ => break,
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
};
|
||||
|
||||
self.advance();
|
||||
let right = self.parse_unary()?;
|
||||
node = Node::BinaryExpr { left: Box::new(node), op, right: Box::new(right) };
|
||||
}
|
||||
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
fn parse_unary(&mut self) -> ParseResult<Node> {
|
||||
let current = self.current()?;
|
||||
if let (Token::ArithmeticOp(ArithmeticOp::Subtract), _) = current {
|
||||
self.advance();
|
||||
let expr = self.parse_unary()?;
|
||||
Ok(Node::UnaryExpr { op: UnaryOp::Negate, expr: Box::new(expr) })
|
||||
} else {
|
||||
self.parse_primary()
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_primary(&mut self) -> ParseResult<Node> {
|
||||
if let (Token::Number { .. }, _) = self.current()? {
|
||||
let (current, loc) = self.current()?;
|
||||
let node = match current {
|
||||
Token::Number { value, is_fractional } => {
|
||||
if *is_fractional {
|
||||
Node::Float(value.parse().unwrap())
|
||||
} else {
|
||||
Node::Integer(value.parse().unwrap())
|
||||
}
|
||||
}
|
||||
_ => return Err(ParseError::UnexpectedToken { token: current.clone(), loc: *loc }),
|
||||
};
|
||||
|
||||
self.advance();
|
||||
Ok(node)
|
||||
} else if self.expect(Token::LeftParen) {
|
||||
self.advance();
|
||||
let node = self.parse_expr()?;
|
||||
if !self.expect(Token::RightParen) {
|
||||
return self.unexpected();
|
||||
}
|
||||
self.advance();
|
||||
Ok(node)
|
||||
} else {
|
||||
self.unexpected()
|
||||
}
|
||||
}
|
||||
|
||||
fn unexpected<T>(&self) -> ParseResult<T> {
|
||||
let (token, loc) = self.current()?;
|
||||
Err(ParseError::UnexpectedToken { token: token.clone(), loc: loc.clone() })
|
||||
}
|
||||
|
||||
pub fn parse(&mut self) -> ParseResult {
|
||||
let mut ast = Vec::new();
|
||||
while !self.is_end() {
|
||||
ast.push(self.parse_expr()?);
|
||||
}
|
||||
|
||||
self.ast = ast;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
type ParseResult<T = ()> = Result<T, ParseError>;
|
||||
|
||||
pub enum ParseError {
|
||||
UnexpectedToken { token: Token, loc: SourceLocation },
|
||||
UnexpectedEnd,
|
||||
}
|
||||
|
||||
impl Display for ParseError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ParseError::UnexpectedToken { token, loc } => {
|
||||
write!(f, "Unexpected token {:?} at {}", token, loc)
|
||||
}
|
||||
ParseError::UnexpectedEnd => write!(f, "Unexpected end of input"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ParseError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::util::expression::{ast::BinaryOp, lexer::Tokenizer};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn parse(input: &str) -> Vec<Node> {
|
||||
let mut tokenizer = Tokenizer::new(input);
|
||||
tokenizer.scan().unwrap();
|
||||
|
||||
let mut parser = Parser::new(tokenizer.tokens);
|
||||
parser.parse().unwrap();
|
||||
|
||||
println!("{:#?}", parser.ast);
|
||||
parser.ast
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parser() {
|
||||
parse("-4 + (2 * 3) % 5 + (2 + 2) * 2");
|
||||
|
||||
assert_eq!(
|
||||
parse("1 + 2 * 3"),
|
||||
vec![Node::BinaryExpr {
|
||||
left: Box::new(Node::Integer(1)),
|
||||
op: BinaryOp::Add,
|
||||
right: Box::new(Node::BinaryExpr {
|
||||
left: Box::new(Node::Integer(2)),
|
||||
op: BinaryOp::Multiply,
|
||||
right: Box::new(Node::Integer(3)),
|
||||
}),
|
||||
}]
|
||||
);
|
||||
}
|
||||
}
|
60
src/util/expression/token.rs
Normal file
60
src/util/expression/token.rs
Normal file
|
@ -0,0 +1,60 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum ArithmeticOp {
|
||||
Add,
|
||||
Subtract,
|
||||
Multiply,
|
||||
Divide,
|
||||
Modulus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum LogicalOp {
|
||||
And,
|
||||
Or,
|
||||
Not,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum ComparisonOp {
|
||||
Equal,
|
||||
NotEqual,
|
||||
LessThan,
|
||||
GreaterThan,
|
||||
LessThanOrEqual,
|
||||
GreaterThanOrEqual,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum BitwiseOp {
|
||||
And,
|
||||
Or,
|
||||
Xor,
|
||||
Not,
|
||||
ShiftLeft,
|
||||
ShiftRight,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum Token {
|
||||
Number { value: String, is_fractional: bool },
|
||||
ArithmeticOp(ArithmeticOp),
|
||||
LogicalOp(LogicalOp),
|
||||
ComparisonOp(ComparisonOp),
|
||||
BitwiseOp(BitwiseOp),
|
||||
LeftParen,
|
||||
RightParen,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct SourceLocation {
|
||||
pub line: usize,
|
||||
pub column: usize,
|
||||
}
|
||||
|
||||
impl Display for SourceLocation {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "line {}, column {}", self.line, self.column)
|
||||
}
|
||||
}
|
68
src/util/expression/vm.rs
Normal file
68
src/util/expression/vm.rs
Normal file
|
@ -0,0 +1,68 @@
|
|||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum VMOpcode {
|
||||
// No operation
|
||||
NOP,
|
||||
// Pop the top value off the stack
|
||||
STPOP,
|
||||
// Loads a specified variable onto the stack
|
||||
STLD(u16),
|
||||
// Pushes a number onto the stack
|
||||
STPSH(f64),
|
||||
// Adds the top (int, int) from the stack
|
||||
ADDII,
|
||||
// Adds the top (float, float) from the stack
|
||||
ADDFF,
|
||||
// Subtracts the (int, int) from the stack
|
||||
SUBII,
|
||||
// Subtracts the (float, float) from the stack
|
||||
SUBFF,
|
||||
// Multiplies the top (int, int) from the stack
|
||||
MULII,
|
||||
// Multiplies the top (float, float) from the stack
|
||||
MULFF,
|
||||
// Divides the top (int, int) from the stack
|
||||
DIVII,
|
||||
// Divides the top (float, float) from the stack
|
||||
DIVFF,
|
||||
// Modulus of the top (int, int) from the stack
|
||||
MODII,
|
||||
// Modulus of the top (float, float) from the stack
|
||||
MODFF,
|
||||
// Calls a function with specified index and passes 1 argument from the stack
|
||||
CALL1(u16),
|
||||
// Calls a function with specified index and passes 2 arguments from the stack
|
||||
CALL2(u16),
|
||||
// Calls a function with specified index and passes 3 arguments from the stack
|
||||
CALL3(u16),
|
||||
|
||||
// Intrinsics
|
||||
// f64(int) -> f64
|
||||
INFLOAT2INT,
|
||||
// int(f64) -> int
|
||||
ININT2FLOAT,
|
||||
// frandom() -> f64
|
||||
INFRAND,
|
||||
// irandom() -> int
|
||||
INIRAND,
|
||||
// floor(f64) -> f64
|
||||
INFLOOR,
|
||||
// ceil(f64) -> f64
|
||||
INCEIL,
|
||||
// round(f64) -> f64
|
||||
INROUND,
|
||||
// abs(f64) -> f64
|
||||
INABS,
|
||||
// min(f64, f64) -> f64
|
||||
INMIN,
|
||||
// max(f64, f64) -> f64
|
||||
INMAX,
|
||||
// sqrt(f64) -> f64
|
||||
INSQRT,
|
||||
// cos(f64) -> f64
|
||||
INCOS,
|
||||
// sin(f64) -> f64
|
||||
INSIN,
|
||||
}
|
||||
|
||||
pub struct VM {
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
pub mod atom;
|
||||
pub mod bitvec;
|
||||
pub mod browser;
|
||||
pub mod expression;
|
||||
pub mod rng;
|
||||
|
|
Loading…
Reference in a new issue