mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2025-07-23 13:00:52 +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" }
|
imgui = { git = "https://github.com/imgui-rs/imgui-rs.git", rev = "67f7f11363e62f09aa0e1288a17800e505860486" }
|
||||||
image = { version = "0.24", default-features = false, features = ["png", "bmp"] }
|
image = { version = "0.24", default-features = false, features = ["png", "bmp"] }
|
||||||
itertools = "0.10"
|
itertools = "0.10"
|
||||||
|
include-flate = "0.3.0"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
lewton = { version = "0.10", optional = true }
|
lewton = { version = "0.10", optional = true }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
use std::{fmt, io};
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
use std::io::SeekFrom;
|
use std::io::SeekFrom;
|
||||||
use std::path::{Component, Path, PathBuf};
|
use std::path::{Component, Path, PathBuf};
|
||||||
|
use std::{fmt, io};
|
||||||
|
|
||||||
|
use include_flate::flate;
|
||||||
|
|
||||||
use crate::framework::error::GameError::FilesystemError;
|
use crate::framework::error::GameError::FilesystemError;
|
||||||
use crate::framework::error::GameResult;
|
use crate::framework::error::GameResult;
|
||||||
use crate::framework::vfs::{OpenOptions, VFile, VFS, VMetadata};
|
use crate::framework::vfs::{OpenOptions, VFile, VMetadata, VFS};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BuiltinFile(Cursor<&'static [u8]>);
|
pub struct BuiltinFile(Cursor<&'static [u8]>);
|
||||||
|
@ -61,7 +63,7 @@ impl VMetadata for BuiltinMetadata {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
enum FSNode {
|
enum FSNode {
|
||||||
File(&'static str, &'static [u8]),
|
File(&'static str, fn() -> &'static [u8]),
|
||||||
Directory(&'static str, Vec<FSNode>),
|
Directory(&'static str, Vec<FSNode>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,14 +77,14 @@ impl FSNode {
|
||||||
|
|
||||||
fn to_file(&self) -> GameResult<Box<dyn VFile>> {
|
fn to_file(&self) -> GameResult<Box<dyn VFile>> {
|
||||||
match self {
|
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))),
|
FSNode::Directory(name, _) => Err(FilesystemError(format!("{} is a directory.", name))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_metadata(&self) -> Box<dyn VMetadata> {
|
fn to_metadata(&self) -> Box<dyn VMetadata> {
|
||||||
match self {
|
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 }),
|
FSNode::Directory(_, _) => Box::new(BuiltinMetadata { is_dir: true, size: 0 }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,58 +94,58 @@ pub struct BuiltinFS {
|
||||||
root: Vec<FSNode>,
|
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 {
|
impl BuiltinFS {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
root: vec![FSNode::Directory(
|
root: vec![FSNode::Directory(
|
||||||
"builtin",
|
"builtin",
|
||||||
vec![
|
vec![
|
||||||
FSNode::File("builtin_font.fnt", include_bytes!("builtin/builtin_font.fnt")),
|
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_0.png", || include_bytes!("builtin/builtin_font_0.png")),
|
||||||
FSNode::File("builtin_font_1.png", include_bytes!("builtin/builtin_font_1.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("gamecontrollerdb.txt", || &GAMECONTROLLERDB_TXT),
|
||||||
FSNode::File("icon.bmp", include_bytes!("../../res/sue.bmp")),
|
FSNode::File("icon.bmp", || &SUE_BMP),
|
||||||
FSNode::File(
|
FSNode::File("organya-wavetable-doukutsu.bin", || &ORGANYA_WAVETABLE_DOUKUTSU_BIN),
|
||||||
"organya-wavetable-doukutsu.bin",
|
FSNode::File("touch.png", || include_bytes!("builtin/touch.png")),
|
||||||
include_bytes!("builtin/organya-wavetable-doukutsu.bin"),
|
|
||||||
),
|
|
||||||
FSNode::File("touch.png", include_bytes!("builtin/touch.png")),
|
|
||||||
FSNode::Directory(
|
FSNode::Directory(
|
||||||
"builtin_data",
|
"builtin_data",
|
||||||
vec![
|
vec![
|
||||||
FSNode::File("buttons.png", include_bytes!("builtin/builtin_data/buttons.png")),
|
FSNode::File("buttons.png", || include_bytes!("builtin/builtin_data/buttons.png")),
|
||||||
FSNode::File("triangles.png", include_bytes!("builtin/builtin_data/triangles.png")),
|
FSNode::File("triangles.png", || include_bytes!("builtin/builtin_data/triangles.png")),
|
||||||
FSNode::Directory(
|
FSNode::Directory(
|
||||||
"headband",
|
"headband",
|
||||||
vec![
|
vec![
|
||||||
FSNode::Directory(
|
FSNode::Directory(
|
||||||
"ogph",
|
"ogph",
|
||||||
vec![
|
vec![
|
||||||
FSNode::File(
|
FSNode::File("Casts.png", || {
|
||||||
"Casts.png",
|
include_bytes!("builtin/builtin_data/headband/ogph/Casts.png")
|
||||||
include_bytes!("builtin/builtin_data/headband/ogph/Casts.png"),
|
}),
|
||||||
),
|
|
||||||
FSNode::Directory(
|
FSNode::Directory(
|
||||||
"Npc",
|
"Npc",
|
||||||
vec![
|
vec![
|
||||||
FSNode::File(
|
FSNode::File("NpcGuest.png", || {
|
||||||
"NpcGuest.png",
|
|
||||||
include_bytes!(
|
include_bytes!(
|
||||||
"builtin/builtin_data/headband/ogph/Npc/NpcGuest.png"
|
"builtin/builtin_data/headband/ogph/Npc/NpcGuest.png"
|
||||||
),
|
)
|
||||||
),
|
}),
|
||||||
FSNode::File(
|
FSNode::File("NpcMiza.png", || {
|
||||||
"NpcMiza.png",
|
|
||||||
include_bytes!(
|
include_bytes!(
|
||||||
"builtin/builtin_data/headband/ogph/Npc/NpcMiza.png"
|
"builtin/builtin_data/headband/ogph/Npc/NpcMiza.png"
|
||||||
),
|
)
|
||||||
),
|
}),
|
||||||
FSNode::File(
|
FSNode::File("NpcRegu.png", || {
|
||||||
"NpcRegu.png",
|
|
||||||
include_bytes!(
|
include_bytes!(
|
||||||
"builtin/builtin_data/headband/ogph/Npc/NpcRegu.png"
|
"builtin/builtin_data/headband/ogph/Npc/NpcRegu.png"
|
||||||
),
|
)
|
||||||
),
|
}),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -151,31 +153,27 @@ impl BuiltinFS {
|
||||||
FSNode::Directory(
|
FSNode::Directory(
|
||||||
"plus",
|
"plus",
|
||||||
vec![
|
vec![
|
||||||
FSNode::File(
|
FSNode::File("Casts.png", || {
|
||||||
"Casts.png",
|
include_bytes!("builtin/builtin_data/headband/plus/casts.png")
|
||||||
include_bytes!("builtin/builtin_data/headband/plus/casts.png"),
|
}),
|
||||||
),
|
|
||||||
FSNode::Directory(
|
FSNode::Directory(
|
||||||
"Npc",
|
"Npc",
|
||||||
vec![
|
vec![
|
||||||
FSNode::File(
|
FSNode::File("NpcGuest.png", || {
|
||||||
"NpcGuest.png",
|
|
||||||
include_bytes!(
|
include_bytes!(
|
||||||
"builtin/builtin_data/headband/plus/npc/npcguest.png"
|
"builtin/builtin_data/headband/plus/npc/npcguest.png"
|
||||||
),
|
)
|
||||||
),
|
}),
|
||||||
FSNode::File(
|
FSNode::File("NpcMiza.png", || {
|
||||||
"NpcMiza.png",
|
|
||||||
include_bytes!(
|
include_bytes!(
|
||||||
"builtin/builtin_data/headband/plus/npc/npcmiza.png"
|
"builtin/builtin_data/headband/plus/npc/npcmiza.png"
|
||||||
),
|
)
|
||||||
),
|
}),
|
||||||
FSNode::File(
|
FSNode::File("NpcRegu.png", || {
|
||||||
"NpcRegu.png",
|
|
||||||
include_bytes!(
|
include_bytes!(
|
||||||
"builtin/builtin_data/headband/plus/npc/npcregu.png"
|
"builtin/builtin_data/headband/plus/npc/npcregu.png"
|
||||||
),
|
)
|
||||||
),
|
}),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -184,16 +182,13 @@ impl BuiltinFS {
|
||||||
),
|
),
|
||||||
FSNode::Directory(
|
FSNode::Directory(
|
||||||
"locale",
|
"locale",
|
||||||
vec![
|
vec![FSNode::File("en.json", || &EN_JSON), FSNode::File("jp.json", || &JP_JSON)],
|
||||||
FSNode::File("en.json", include_bytes!("builtin/builtin_data/locale/en.json")),
|
|
||||||
FSNode::File("jp.json", include_bytes!("builtin/builtin_data/locale/jp.json")),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
FSNode::Directory(
|
FSNode::Directory(
|
||||||
"lightmap",
|
"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())
|
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) {
|
match self.get_node(path) {
|
||||||
Ok(FSNode::Directory(_, contents)) => {
|
Ok(FSNode::Directory(_, contents)) => {
|
||||||
let mut vec = Vec::new();
|
let mut vec = Vec::new();
|
||||||
|
@ -302,15 +297,15 @@ impl VFS for BuiltinFS {
|
||||||
fn test_builtin_fs() {
|
fn test_builtin_fs() {
|
||||||
let fs = BuiltinFS {
|
let fs = BuiltinFS {
|
||||||
root: vec![
|
root: vec![
|
||||||
FSNode::File("test.txt", &[]),
|
FSNode::File("test.txt", || &[]),
|
||||||
FSNode::Directory(
|
FSNode::Directory(
|
||||||
"memes",
|
"memes",
|
||||||
vec![
|
vec![
|
||||||
FSNode::File("nothing.txt", &[]),
|
FSNode::File("nothing.txt", || &[]),
|
||||||
FSNode::Directory("secret stuff", vec![FSNode::File("passwords.txt", b"12345678")]),
|
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 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::common::{Color, Rect};
|
||||||
use crate::framework::context::Context;
|
|
||||||
use crate::framework::error::GameResult;
|
|
||||||
use crate::framework::graphics::{BlendMode, VSyncMode};
|
|
||||||
use crate::game::Game;
|
use crate::game::Game;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
@ -46,7 +48,7 @@ pub trait BackendRenderer {
|
||||||
|
|
||||||
fn present(&mut self) -> GameResult;
|
fn present(&mut self) -> GameResult;
|
||||||
|
|
||||||
fn set_vsync_mode(&mut self, _mode: VSyncMode) -> GameResult {
|
fn set_swap_mode(&mut self, _mode: SwapMode) -> GameResult {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,26 +109,26 @@ pub trait BackendGamepad {
|
||||||
#[allow(unreachable_code)]
|
#[allow(unreachable_code)]
|
||||||
pub fn init_backend(headless: bool, size_hint: (u16, u16)) -> GameResult<Box<dyn Backend>> {
|
pub fn init_backend(headless: bool, size_hint: (u16, u16)) -> GameResult<Box<dyn Backend>> {
|
||||||
if headless {
|
if headless {
|
||||||
return crate::framework::backend_null::NullBackend::new();
|
return super::backend_null::NullBackend::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "backend-horizon"))]
|
#[cfg(all(feature = "backend-horizon"))]
|
||||||
{
|
{
|
||||||
return crate::framework::backend_horizon::HorizonBackend::new();
|
return super::backend_horizon::HorizonBackend::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "backend-winit"))]
|
#[cfg(all(feature = "backend-winit"))]
|
||||||
{
|
{
|
||||||
return crate::framework::backend_winit::WinitBackend::new();
|
return super::backend_winit::WinitBackend::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "backend-sdl")]
|
#[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.");
|
log::warn!("No backend compiled in, using null backend instead.");
|
||||||
crate::framework::backend_null::NullBackend::new()
|
super::backend_null::NullBackend::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum SpriteBatchCommand {
|
pub enum SpriteBatchCommand {
|
||||||
|
|
|
@ -32,13 +32,13 @@ use crate::framework::backend::{
|
||||||
};
|
};
|
||||||
use crate::framework::context::Context;
|
use crate::framework::context::Context;
|
||||||
use crate::framework::error::{GameError, GameResult};
|
use crate::framework::error::{GameError, GameResult};
|
||||||
use crate::framework::filesystem;
|
|
||||||
use crate::framework::gamepad::{Axis, Button, GamepadType};
|
use crate::framework::gamepad::{Axis, Button, GamepadType};
|
||||||
use crate::framework::graphics::BlendMode;
|
use crate::framework::graphics::{BlendMode, SwapMode};
|
||||||
use crate::framework::keyboard::ScanCode;
|
use crate::framework::keyboard::ScanCode;
|
||||||
#[cfg(feature = "render-opengl")]
|
#[cfg(feature = "render-opengl")]
|
||||||
use crate::framework::render_opengl::{GLContext, OpenGLRenderer};
|
use crate::framework::render_opengl::{GLContext, OpenGLRenderer};
|
||||||
use crate::framework::ui::init_imgui;
|
use crate::framework::ui::init_imgui;
|
||||||
|
use crate::framework::{filesystem, render_opengl};
|
||||||
use crate::game::shared_game_state::WindowMode;
|
use crate::game::shared_game_state::WindowMode;
|
||||||
use crate::game::Game;
|
use crate::game::Game;
|
||||||
use crate::game::GAME_SUSPENDED;
|
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_Delete as usize] = Scancode::Delete as u32;
|
||||||
key_map[ImGuiKey_Enter as usize] = Scancode::Return 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;
|
impl render_opengl::GLPlatformFunctions for SDL2GLPlatform {
|
||||||
|
fn get_proc_address(&self, name: &str) -> *const c_void {
|
||||||
unsafe fn get_proc_address(user_data: &mut *mut c_void, name: &str) -> *const c_void {
|
let refs = self.0.borrow();
|
||||||
let refs = Rc::from_raw(*user_data as *mut RefCell<SDL2Context>);
|
|
||||||
|
|
||||||
let result = {
|
|
||||||
let refs = &mut *refs.as_ptr();
|
|
||||||
refs.video.gl_get_proc_address(name) as *const _
|
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();
|
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 =
|
let platform = Box::new(SDL2GLPlatform(self.refs.clone()));
|
||||||
GLContext { gles2_mode: false, is_sdl: true, get_proc_address, swap_buffers, user_data, ctx };
|
let gl_context: GLContext = GLContext { gles2_mode: false, platform, ctx };
|
||||||
|
|
||||||
return Ok(Box::new(OpenGLRenderer::new(gl_context, imgui)));
|
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 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 struct Context {
|
||||||
pub headless: bool,
|
pub headless: bool,
|
||||||
pub shutdown_requested: bool,
|
pub shutdown_requested: bool,
|
||||||
|
@ -18,7 +19,7 @@ pub struct Context {
|
||||||
pub(crate) real_screen_size: (u32, u32),
|
pub(crate) real_screen_size: (u32, u32),
|
||||||
pub(crate) screen_size: (f32, f32),
|
pub(crate) screen_size: (f32, f32),
|
||||||
pub(crate) screen_insets: (f32, f32, f32, f32),
|
pub(crate) screen_insets: (f32, f32, f32, f32),
|
||||||
pub(crate) vsync_mode: VSyncMode,
|
pub(crate) swap_mode: SwapMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
|
@ -35,7 +36,7 @@ impl Context {
|
||||||
real_screen_size: (320, 240),
|
real_screen_size: (320, 240),
|
||||||
screen_size: (320.0, 240.0),
|
screen_size: (320.0, 240.0),
|
||||||
screen_insets: (0.0, 0.0, 0.0, 0.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,
|
Multiply,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
pub enum VSyncMode {
|
pub enum SwapMode {
|
||||||
/// No V-Sync - uncapped frame rate
|
Immediate = 0,
|
||||||
Uncapped,
|
VSync = 1,
|
||||||
/// Synchronized to V-Sync
|
Adaptive = -1,
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear(ctx: &mut Context, color: Color) {
|
pub fn clear(ctx: &mut Context, color: Color) {
|
||||||
|
@ -51,10 +44,12 @@ pub fn present(ctx: &mut Context) -> GameResult {
|
||||||
Ok(())
|
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 {
|
if let Some(renderer) = &mut ctx.renderer {
|
||||||
ctx.vsync_mode = mode;
|
if ctx.swap_mode != mode {
|
||||||
renderer.set_vsync_mode(mode);
|
ctx.swap_mode = mode;
|
||||||
|
renderer.set_swap_mode(mode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
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()))
|
Err(GameError::RenderError("Rendering backend hasn't been initialized yet.".to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn imgui_context(ctx: &Context) -> GameResult<&mut imgui::Context> {
|
pub fn imgui_context(ctx: &Context) -> GameResult<&mut imgui::Context> {
|
||||||
if let Some(renderer) = ctx.renderer.as_ref() {
|
if let Some(renderer) = ctx.renderer.as_ref() {
|
||||||
return renderer.imgui();
|
return renderer.imgui();
|
||||||
|
|
|
@ -1,38 +1,40 @@
|
||||||
use std::any::Any;
|
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::ffi::{c_void, CStr};
|
||||||
use std::hint::unreachable_unchecked;
|
use std::hint::unreachable_unchecked;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
use std::ptr::null;
|
use std::ptr::null;
|
||||||
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use imgui::{DrawCmd, DrawCmdParams, DrawData, DrawIdx, DrawVert, TextureId, Ui};
|
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::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;
|
use crate::game::GAME_SUSPENDED;
|
||||||
|
|
||||||
pub trait GLPlatformFunctions {
|
pub trait GLPlatformFunctions {
|
||||||
fn get_proc_address(&self, name: &str) -> *const c_void;
|
fn get_proc_address(&self, name: &str) -> *const c_void;
|
||||||
|
|
||||||
fn swap_buffers(&self);
|
fn swap_buffers(&self);
|
||||||
|
|
||||||
|
fn set_swap_mode(&self, mode: SwapMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GLContext {
|
pub struct GLContext {
|
||||||
pub gles2_mode: bool,
|
pub gles2_mode: bool,
|
||||||
pub is_sdl: bool,
|
pub platform: Box<dyn GLPlatformFunctions>,
|
||||||
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 ctx: *mut Context,
|
pub ctx: *mut Context,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +46,7 @@ pub struct OpenGLTexture {
|
||||||
shader: RenderShader,
|
shader: RenderShader,
|
||||||
vbo: GLuint,
|
vbo: GLuint,
|
||||||
vertices: Vec<VertexData>,
|
vertices: Vec<VertexData>,
|
||||||
context_active: Arc<RefCell<bool>>,
|
gl: Rc<Gl>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BackendTexture for OpenGLTexture {
|
impl BackendTexture for OpenGLTexture {
|
||||||
|
@ -224,38 +226,35 @@ impl BackendTexture for OpenGLTexture {
|
||||||
|
|
||||||
fn draw(&mut self) -> GameResult {
|
fn draw(&mut self) -> GameResult {
|
||||||
unsafe {
|
unsafe {
|
||||||
if let Some(gl) = &GL_PROC {
|
let gl = self.gl.as_ref();
|
||||||
if self.texture_id == 0 {
|
if self.texture_id == 0 {
|
||||||
return Ok(());
|
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()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
impl Drop for OpenGLTexture {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if *self.context_active.as_ref().borrow() {
|
unsafe {
|
||||||
unsafe {
|
let gl = self.gl.as_ref();
|
||||||
if let Some(gl) = &GL_PROC {
|
if !*gl.context_active.borrow() {
|
||||||
if self.texture_id != 0 {
|
return;
|
||||||
let texture_id = &self.texture_id;
|
}
|
||||||
gl.gl.DeleteTextures(1, texture_id as *const _);
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
// 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.render_fbo = return_param(|x| gl.gl.GetIntegerv(gl::FRAMEBUFFER_BINDING, x));
|
||||||
|
|
||||||
self.tex_shader =
|
self.tex_shader = RenderShader::compile(gl, vshdr_basic, fshdr_tex).unwrap_or_else(|e| {
|
||||||
RenderShader::compile(gl, vshdr_basic, fshdr_tex).unwrap_or_else(|e| {
|
log::error!("Failed to compile texture shader: {}", e);
|
||||||
log::error!("Failed to compile texture shader: {}", e);
|
RenderShader::default()
|
||||||
RenderShader::default()
|
});
|
||||||
});
|
self.fill_shader = RenderShader::compile(gl, vshdr_basic, fshdr_fill).unwrap_or_else(|e| {
|
||||||
self.fill_shader =
|
log::error!("Failed to compile fill shader: {}", e);
|
||||||
RenderShader::compile(gl, vshdr_basic, fshdr_fill).unwrap_or_else(|e| {
|
RenderShader::default()
|
||||||
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);
|
||||||
self.fill_water_shader =
|
RenderShader::default()
|
||||||
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.vbo = return_param(|x| gl.gl.GenBuffers(1, x));
|
||||||
self.ebo = 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 struct Gl {
|
||||||
pub gl: gl::Gles2,
|
pub gl: gl::Gles2,
|
||||||
|
pub context_active: RefCell<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
static mut GL_PROC: Option<Gl> = None;
|
pub fn load_gl(gl_context: &mut GLContext) -> Gl {
|
||||||
|
|
||||||
pub fn load_gl(gl_context: &mut GLContext) -> &'static Gl {
|
|
||||||
unsafe {
|
unsafe {
|
||||||
if let Some(gl) = &GL_PROC {
|
let gl = gl::Gles2::load_with(|ptr| gl_context.platform.get_proc_address(ptr));
|
||||||
return gl;
|
|
||||||
}
|
|
||||||
|
|
||||||
let gl = gl::Gles2::load_with(|ptr| (gl_context.get_proc_address)(&mut gl_context.user_data, ptr));
|
|
||||||
|
|
||||||
let version = {
|
let version = {
|
||||||
let p = gl.GetString(gl::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);
|
log::info!("OpenGL version {}", version);
|
||||||
|
|
||||||
GL_PROC = Some(Gl { gl });
|
Gl { gl, context_active: RefCell::new(true) }
|
||||||
GL_PROC.as_ref().unwrap()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OpenGLRenderer {
|
pub struct OpenGLRenderer {
|
||||||
refs: GLContext,
|
refs: GLContext,
|
||||||
|
gl: Option<Rc<Gl>>,
|
||||||
imgui: UnsafeCell<imgui::Context>,
|
imgui: UnsafeCell<imgui::Context>,
|
||||||
render_data: RenderData,
|
render_data: RenderData,
|
||||||
context_active: Arc<RefCell<bool>>,
|
|
||||||
def_matrix: [[f32; 4]; 4],
|
def_matrix: [[f32; 4]; 4],
|
||||||
curr_matrix: [[f32; 4]; 4],
|
curr_matrix: [[f32; 4]; 4],
|
||||||
}
|
}
|
||||||
|
@ -617,22 +611,25 @@ impl OpenGLRenderer {
|
||||||
pub fn new(refs: GLContext, imgui: imgui::Context) -> OpenGLRenderer {
|
pub fn new(refs: GLContext, imgui: imgui::Context) -> OpenGLRenderer {
|
||||||
OpenGLRenderer {
|
OpenGLRenderer {
|
||||||
refs,
|
refs,
|
||||||
|
gl: None,
|
||||||
imgui: UnsafeCell::new(imgui),
|
imgui: UnsafeCell::new(imgui),
|
||||||
render_data: RenderData::new(),
|
render_data: RenderData::new(),
|
||||||
context_active: Arc::new(RefCell::new(true)),
|
|
||||||
def_matrix: [[0.0; 4]; 4],
|
def_matrix: [[0.0; 4]; 4],
|
||||||
curr_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 imgui = unsafe { &mut *self.imgui.get() };
|
||||||
|
|
||||||
let gles2 = self.refs.gles2_mode;
|
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 {
|
if !self.render_data.initialized {
|
||||||
self.render_data.init(gles2, imgui, gl);
|
self.render_data.init(gles2, imgui, &gl);
|
||||||
}
|
}
|
||||||
|
|
||||||
Some((&mut self.refs, gl))
|
Some((&mut self.refs, gl))
|
||||||
|
@ -674,7 +671,7 @@ impl BackendRenderer for OpenGLRenderer {
|
||||||
let matrix =
|
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]];
|
[[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 _);
|
gl.gl.UniformMatrix4fv(self.render_data.tex_shader.proj_mtx, 1, gl::FALSE, matrix.as_ptr() as _);
|
||||||
|
|
||||||
let color = (255, 255, 255, 255);
|
let color = (255, 255, 255, 255);
|
||||||
|
@ -693,46 +690,24 @@ impl BackendRenderer for OpenGLRenderer {
|
||||||
self.render_data.surf_texture,
|
self.render_data.surf_texture,
|
||||||
BackendShader::Texture,
|
BackendShader::Texture,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
//gl.gl.Finish();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((context, _)) = self.get_context() {
|
if let Some((context, _)) = self.get_context() {
|
||||||
(context.swap_buffers)(&mut context.user_data);
|
context.platform.swap_buffers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_vsync_mode(&mut self, mode: VSyncMode) -> GameResult {
|
fn set_swap_mode(&mut self, mode: SwapMode) -> GameResult {
|
||||||
if !self.refs.is_sdl {
|
if let Some((ctx, _)) = self.get_context() {
|
||||||
return Ok(());
|
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 {
|
fn prepare_draw(&mut self, width: f32, height: f32) -> GameResult {
|
||||||
if let Some((_, gl)) = self.get_context() {
|
if let Some((_, gl)) = self.get_context() {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -854,7 +829,7 @@ impl BackendRenderer for OpenGLRenderer {
|
||||||
vertices: Vec::new(),
|
vertices: Vec::new(),
|
||||||
shader: self.render_data.tex_shader,
|
shader: self.render_data.tex_shader,
|
||||||
vbo: self.render_data.vbo,
|
vbo: self.render_data.vbo,
|
||||||
context_active: self.context_active.clone(),
|
gl: gl.clone(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -893,7 +868,7 @@ impl BackendRenderer for OpenGLRenderer {
|
||||||
vertices: Vec::new(),
|
vertices: Vec::new(),
|
||||||
shader: self.render_data.tex_shader,
|
shader: self.render_data.tex_shader,
|
||||||
vbo: self.render_data.vbo,
|
vbo: self.render_data.vbo,
|
||||||
context_active: self.context_active.clone(),
|
gl: gl.clone(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1009,7 +984,7 @@ impl BackendRenderer for OpenGLRenderer {
|
||||||
|
|
||||||
fn draw_rect(&mut self, rect: Rect<isize>, color: Color) -> GameResult {
|
fn draw_rect(&mut self, rect: Rect<isize>, color: Color) -> GameResult {
|
||||||
unsafe {
|
unsafe {
|
||||||
if let Some(gl) = &GL_PROC {
|
if let Some((_, gl)) = self.get_context() {
|
||||||
let color = color.to_rgba();
|
let color = color.to_rgba();
|
||||||
let mut uv = self.render_data.font_tex_size;
|
let mut uv = self.render_data.font_tex_size;
|
||||||
uv.0 = 0.0 / uv.0;
|
uv.0 = 0.0 / uv.0;
|
||||||
|
@ -1024,7 +999,7 @@ impl BackendRenderer for OpenGLRenderer {
|
||||||
VertexData { position: (rect.right as _, rect.bottom as _), uv, color },
|
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.BindTexture(gl::TEXTURE_2D, self.render_data.font_texture);
|
||||||
gl.gl.BindBuffer(gl::ARRAY_BUFFER, self.render_data.vbo);
|
gl.gl.BindBuffer(gl::ARRAY_BUFFER, self.render_data.vbo);
|
||||||
|
@ -1262,16 +1237,16 @@ impl OpenGLRenderer {
|
||||||
mut texture: u32,
|
mut texture: u32,
|
||||||
shader: BackendShader,
|
shader: BackendShader,
|
||||||
) -> GameResult<()> {
|
) -> GameResult<()> {
|
||||||
if let Some(gl) = &GL_PROC {
|
if let Some((_, gl)) = self.get_context() {
|
||||||
match shader {
|
match shader {
|
||||||
BackendShader::Fill => {
|
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 => {
|
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) => {
|
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.scale, scale);
|
||||||
gl.gl.Uniform1f(self.render_data.fill_water_shader.time, t);
|
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);
|
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 {
|
impl Drop for OpenGLRenderer {
|
||||||
fn drop(&mut self) {
|
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::context::Context;
|
||||||
use crate::framework::error::GameResult;
|
use crate::framework::error::GameResult;
|
||||||
use crate::framework::graphics;
|
use crate::framework::graphics::{self, SwapMode};
|
||||||
use crate::framework::graphics::VSyncMode;
|
|
||||||
use crate::framework::ui::UI;
|
use crate::framework::ui::UI;
|
||||||
use crate::game::filesystem_container::FilesystemContainer;
|
use crate::game::filesystem_container::FilesystemContainer;
|
||||||
|
use crate::game::settings::VSyncMode;
|
||||||
use crate::game::shared_game_state::{Fps, SharedGameState, TimingMode};
|
use crate::game::shared_game_state::{Fps, SharedGameState, TimingMode};
|
||||||
use crate::graphics::texture_set::{G_MAG, I_MAG};
|
use crate::graphics::texture_set::{G_MAG, I_MAG};
|
||||||
use crate::scene::loading_scene::LoadingScene;
|
use crate::scene::loading_scene::LoadingScene;
|
||||||
|
@ -61,7 +61,7 @@ pub struct Game {
|
||||||
impl Game {
|
impl Game {
|
||||||
fn new(ctx: &mut Context) -> GameResult<Game> {
|
fn new(ctx: &mut Context) -> GameResult<Game> {
|
||||||
let s = Game {
|
let s = Game {
|
||||||
scene: RefCell::new( None),
|
scene: RefCell::new(None),
|
||||||
ui: UI::new(ctx)?,
|
ui: UI::new(ctx)?,
|
||||||
state: RefCell::new(SharedGameState::new(ctx)?),
|
state: RefCell::new(SharedGameState::new(ctx)?),
|
||||||
start_time: Instant::now(),
|
start_time: Instant::now(),
|
||||||
|
@ -80,14 +80,12 @@ impl Game {
|
||||||
let state_ref = self.state.get_mut();
|
let state_ref = self.state.get_mut();
|
||||||
|
|
||||||
if let Some(scene) = self.scene.get_mut() {
|
if let Some(scene) = self.scene.get_mut() {
|
||||||
|
let speed =
|
||||||
let speed = if state_ref.textscript_vm.mode == ScriptMode::Map
|
if state_ref.textscript_vm.mode == ScriptMode::Map && state_ref.textscript_vm.flags.cutscene_skip() {
|
||||||
&& state_ref.textscript_vm.flags.cutscene_skip()
|
4.0
|
||||||
{
|
} else {
|
||||||
4.0
|
1.0
|
||||||
} else {
|
} * state_ref.settings.speed;
|
||||||
1.0
|
|
||||||
} * state_ref.settings.speed;
|
|
||||||
|
|
||||||
match state_ref.settings.timing_mode {
|
match state_ref.settings.timing_mode {
|
||||||
TimingMode::_50Hz | TimingMode::_60Hz => {
|
TimingMode::_50Hz | TimingMode::_60Hz => {
|
||||||
|
@ -142,14 +140,21 @@ impl Game {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn draw(&mut self, ctx: &mut Context) -> GameResult {
|
pub(crate) fn draw(&mut self, ctx: &mut Context) -> GameResult {
|
||||||
match ctx.vsync_mode {
|
let vsync_mode = self.state.get_mut().settings.vsync_mode;
|
||||||
VSyncMode::Uncapped | VSyncMode::VSync => {
|
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;
|
self.present = true;
|
||||||
}
|
}
|
||||||
_ => unsafe {
|
_ => unsafe {
|
||||||
|
graphics::set_swap_mode(ctx, SwapMode::Adaptive)?;
|
||||||
self.present = false;
|
self.present = false;
|
||||||
|
|
||||||
let divisor = match ctx.vsync_mode {
|
let divisor = match vsync_mode {
|
||||||
VSyncMode::VRRTickSync1x => 1,
|
VSyncMode::VRRTickSync1x => 1,
|
||||||
VSyncMode::VRRTickSync2x => 2,
|
VSyncMode::VRRTickSync2x => 2,
|
||||||
VSyncMode::VRRTickSync3x => 3,
|
VSyncMode::VRRTickSync3x => 3,
|
||||||
|
@ -194,7 +199,8 @@ impl Game {
|
||||||
|
|
||||||
let n1 = (elapsed - self.last_tick) as f64;
|
let n1 = (elapsed - self.last_tick) as f64;
|
||||||
let n2 = (self.next_tick - 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 {
|
unsafe {
|
||||||
G_MAG = if self.state.get_mut().settings.subpixel_coords { self.state.get_mut().scale } else { 1.0 };
|
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::error::GameResult;
|
||||||
use crate::framework::filesystem::{user_create, user_open};
|
use crate::framework::filesystem::{user_create, user_open};
|
||||||
use crate::framework::gamepad::{Axis, AxisDirection, Button, PlayerControllerInputType};
|
use crate::framework::gamepad::{Axis, AxisDirection, Button, PlayerControllerInputType};
|
||||||
use crate::framework::graphics::VSyncMode;
|
|
||||||
use crate::framework::keyboard::ScanCode;
|
use crate::framework::keyboard::ScanCode;
|
||||||
use crate::game::player::TargetPlayer;
|
use crate::game::player::TargetPlayer;
|
||||||
use crate::game::shared_game_state::{CutsceneSkipMode, ScreenShakeIntensity, TimingMode, WindowMode};
|
use crate::game::shared_game_state::{CutsceneSkipMode, ScreenShakeIntensity, TimingMode, WindowMode};
|
||||||
|
@ -582,6 +581,20 @@ pub fn default_controller_axis_sensitivity() -> f64 {
|
||||||
0.3
|
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)]
|
#[derive(serde::Serialize, serde::Deserialize, Copy, Clone, PartialEq, Eq)]
|
||||||
pub enum LightingEngine {
|
pub enum LightingEngine {
|
||||||
Default,
|
Default,
|
||||||
|
|
|
@ -2,9 +2,9 @@ use itertools::Itertools;
|
||||||
|
|
||||||
use crate::framework::context::Context;
|
use crate::framework::context::Context;
|
||||||
use crate::framework::error::GameResult;
|
use crate::framework::error::GameResult;
|
||||||
use crate::framework::graphics::VSyncMode;
|
use crate::framework::filesystem;
|
||||||
use crate::framework::{filesystem, graphics};
|
|
||||||
use crate::game::settings::LightingEngine;
|
use crate::game::settings::LightingEngine;
|
||||||
|
use crate::game::settings::VSyncMode;
|
||||||
use crate::game::shared_game_state::{CutsceneSkipMode, ScreenShakeIntensity, SharedGameState, TimingMode, WindowMode};
|
use crate::game::shared_game_state::{CutsceneSkipMode, ScreenShakeIntensity, SharedGameState, TimingMode, WindowMode};
|
||||||
use crate::graphics::font::Font;
|
use crate::graphics::font::Font;
|
||||||
use crate::input::combined_menu_controller::CombinedMenuController;
|
use crate::input::combined_menu_controller::CombinedMenuController;
|
||||||
|
@ -725,7 +725,6 @@ impl SettingsMenu {
|
||||||
|
|
||||||
*value = new_value;
|
*value = new_value;
|
||||||
state.settings.vsync_mode = new_mode;
|
state.settings.vsync_mode = new_mode;
|
||||||
graphics::set_vsync_mode(ctx, new_mode)?;
|
|
||||||
|
|
||||||
let _ = state.settings.save(ctx);
|
let _ = state.settings.save(ctx);
|
||||||
}
|
}
|
||||||
|
@ -742,7 +741,6 @@ impl SettingsMenu {
|
||||||
|
|
||||||
*value = new_value;
|
*value = new_value;
|
||||||
state.settings.vsync_mode = new_mode;
|
state.settings.vsync_mode = new_mode;
|
||||||
graphics::set_vsync_mode(ctx, new_mode)?;
|
|
||||||
|
|
||||||
let _ = state.settings.save(ctx);
|
let _ = state.settings.save(ctx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::framework::context::Context;
|
use crate::framework::context::Context;
|
||||||
use crate::framework::error::GameResult;
|
use crate::framework::error::GameResult;
|
||||||
use crate::framework::graphics;
|
|
||||||
use crate::game::shared_game_state::SharedGameState;
|
use crate::game::shared_game_state::SharedGameState;
|
||||||
use crate::scene::no_data_scene::NoDataScene;
|
use crate::scene::no_data_scene::NoDataScene;
|
||||||
use crate::scene::Scene;
|
use crate::scene::Scene;
|
||||||
|
@ -44,8 +43,6 @@ impl Scene for LoadingScene {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
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") {
|
match state.texture_set.get_or_load_batch(ctx, &state.constants, "Loading") {
|
||||||
Ok(batch) => {
|
Ok(batch) => {
|
||||||
batch.add(
|
batch.add(
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use std::{
|
use std::{
|
||||||
cell::UnsafeCell,
|
cell::UnsafeCell, fmt::{self, Debug, Display, Formatter}, hash::{Hash, Hasher}, sync::{Mutex, OnceLock}
|
||||||
sync::{Mutex, OnceLock},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const BUCKET_BITS: usize = 10;
|
const BUCKET_BITS: usize = 10;
|
||||||
|
@ -52,7 +51,9 @@ const fn string_hash(data: &str) -> HashType {
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
let bytes = data.as_bytes();
|
let bytes = data.as_bytes();
|
||||||
while i < bytes.len() {
|
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 ^= byte;
|
||||||
hash = hash.wrapping_mul(0x01000193u32);
|
hash = hash.wrapping_mul(0x01000193u32);
|
||||||
i += 1;
|
i += 1;
|
||||||
|
@ -69,11 +70,11 @@ impl AtomStore {
|
||||||
fn find(&self, data: &str) -> Option<Atom> {
|
fn find(&self, data: &str) -> Option<Atom> {
|
||||||
let hash = string_hash(data);
|
let hash = string_hash(data);
|
||||||
let bucket = (hash as usize) & BUCKET_MASK;
|
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) };
|
let bucket = unsafe { self.buckets.get_unchecked(bucket) };
|
||||||
|
|
||||||
if let Some(atom) = bucket {
|
if let Some(atom) = bucket {
|
||||||
let iter = atom.iter();
|
for atom in atom.iter() {
|
||||||
for atom in iter {
|
|
||||||
if atom.data == data {
|
if atom.data == data {
|
||||||
return Some(Atom(atom));
|
return Some(Atom(atom));
|
||||||
}
|
}
|
||||||
|
@ -95,12 +96,13 @@ impl AtomStore {
|
||||||
fn insert(&mut self, data: &'static str) -> Atom {
|
fn insert(&mut self, data: &'static str) -> Atom {
|
||||||
let hash = string_hash(data);
|
let hash = string_hash(data);
|
||||||
let bucket = (hash as usize) & BUCKET_MASK;
|
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) };
|
let bucket = unsafe { self.buckets.get_unchecked_mut(bucket) };
|
||||||
|
|
||||||
if let Some(atom) = bucket {
|
if let Some(atom) = bucket {
|
||||||
let iter = atom.iter();
|
|
||||||
let mut prev_atom = *atom;
|
let mut prev_atom = *atom;
|
||||||
for atom in iter {
|
for atom in atom.iter() {
|
||||||
if atom.data == data {
|
if atom.data == data {
|
||||||
return Atom(atom);
|
return Atom(atom);
|
||||||
}
|
}
|
||||||
|
@ -112,6 +114,7 @@ impl AtomStore {
|
||||||
unsafe {
|
unsafe {
|
||||||
*prev_atom.next.get() = Some(new_atom);
|
*prev_atom.next.get() = Some(new_atom);
|
||||||
}
|
}
|
||||||
|
|
||||||
Atom(new_atom)
|
Atom(new_atom)
|
||||||
} else {
|
} else {
|
||||||
let atom = AtomData::new(data, hash);
|
let atom = AtomData::new(data, hash);
|
||||||
|
@ -119,42 +122,24 @@ impl AtomStore {
|
||||||
Atom(atom)
|
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 {
|
impl Debug for AtomStore {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.write_str("AtomStore")?;
|
f.write_str("AtomStore")?;
|
||||||
|
|
||||||
#[cfg(any(test, debug_assertions))]
|
#[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();
|
let mut dbg = f.debug_map();
|
||||||
for (i, bucket) in self.buckets.iter().enumerate() {
|
for (i, bucket) in self.buckets.iter().enumerate() {
|
||||||
if let Some(atom) = bucket {
|
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));
|
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();
|
static ATOM_STORE: OnceLock<Mutex<Box<AtomStore>>> = OnceLock::new();
|
||||||
|
|
||||||
fn get_atom_store() -> &'static Mutex<Box<AtomStore>> {
|
fn get_atom_store() -> &'static Mutex<Box<AtomStore>> {
|
||||||
|
@ -199,23 +185,24 @@ impl PartialEq for Atom {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::hash::Hash for Atom {
|
impl Hash for Atom {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
unsafe { (*self.0).hash.hash(state) }
|
self.data().hash.hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Atom {
|
impl Debug for Atom {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
|
||||||
f.write_str("Atom(")?;
|
f.write_str("Atom(")?;
|
||||||
self.data().data.fmt(f)?;
|
Debug::fmt(self.data().data, f)?;
|
||||||
f.write_str(")")
|
f.write_str(")")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Atom {
|
impl Display for Atom {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
self.data().data.fmt(f)
|
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 atom;
|
||||||
pub mod bitvec;
|
pub mod bitvec;
|
||||||
pub mod browser;
|
pub mod browser;
|
||||||
|
pub mod expression;
|
||||||
pub mod rng;
|
pub mod rng;
|
||||||
|
|
Loading…
Reference in a new issue