1
0
Fork 0
mirror of https://github.com/doukutsu-rs/doukutsu-rs synced 2024-11-21 21:22:44 +00:00

Compare commits

...

5 commits

Author SHA1 Message Date
Alula 0db8ab0308
Remove GLContext.is_sdl 2024-08-27 17:10:43 +02:00
Alula 56832514eb
Cleanup OpenGL backend and graphics code 2024-08-27 17:07:21 +02:00
Alula 1b7a64b5d9
Add compression for BuiltinFS (with lazy decompression) 2024-08-27 16:11:58 +02:00
Alula d41fa57ca2
wip: expression parser 2024-08-27 15:48:56 +02:00
Alula 067376c74f
make this shit cleaner 2024-08-15 05:36:46 +02:00
19 changed files with 909 additions and 308 deletions

View file

@ -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"

View file

@ -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", || &[]),
],
};

View file

@ -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 {

View file

@ -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)));
}

View file

@ -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,
}
}

View file

@ -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();

View file

@ -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);
}
}
}

View file

@ -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 };

View file

@ -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,

View file

@ -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);
}

View file

@ -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(

View file

@ -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)
}
}

View 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> },
}

View 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,
],
);
}
}

View file

@ -0,0 +1,5 @@
pub mod ast;
pub mod lexer;
pub mod parser;
pub mod token;
pub mod vm;

View 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)),
}),
}]
);
}
}

View 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
View 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 {
}

View file

@ -1,4 +1,5 @@
pub mod atom;
pub mod bitvec;
pub mod browser;
pub mod expression;
pub mod rng;