From 26bfcc7a94379b8eb9ca72e0a4b2673df4826cca Mon Sep 17 00:00:00 2001 From: John Doty Date: Fri, 30 Jun 2023 16:24:54 -0700 Subject: [PATCH] [oden] Time, path searching, game directory --- {src => game}/happy-tree.png | Bin {src => game}/main.ts | 10 +++-- src/script.rs | 18 +++++++-- src/script/io.rs | 74 ++++++++++++++++++++++++++++++++++- src/script/time.rs | 65 ++++++++++++++++++++++++++++++ src/time.ts | 13 ++++++ tsconfig.json | 14 +++++-- types/time-core.d.ts | 4 ++ 8 files changed, 185 insertions(+), 13 deletions(-) rename {src => game}/happy-tree.png (100%) rename {src => game}/main.ts (56%) create mode 100644 src/script/time.rs create mode 100644 src/time.ts create mode 100644 types/time-core.d.ts diff --git a/src/happy-tree.png b/game/happy-tree.png similarity index 100% rename from src/happy-tree.png rename to game/happy-tree.png diff --git a/src/main.ts b/game/main.ts similarity index 56% rename from src/main.ts rename to game/main.ts index b1f49aba..97fef854 100644 --- a/src/main.ts +++ b/game/main.ts @@ -1,5 +1,6 @@ -import { cls, print, spr, use_texture } from "./graphics.ts"; -import { load_texture } from "./assets.ts"; +import { cls, print, spr, use_texture } from "./graphics"; +import { load_texture } from "./assets"; +import { since_start, since_last_frame } from "./time"; let the_texture: number | undefined = undefined; @@ -7,7 +8,10 @@ export function init() { print("Hello world!"); // Start this load, but then... - load_texture("./src/happy-tree.png").then((n) => (the_texture = n)); + load_texture("./happy-tree.png").then((n) => { + print("Tree loaded at", since_start()); + the_texture = n; + }); } export function update() {} diff --git a/src/script.rs b/src/script.rs index 3eecfd58..e9ef21f1 100644 --- a/src/script.rs +++ b/src/script.rs @@ -3,8 +3,8 @@ use oden_js::{ Context, ContextRef, Result, Runtime, Value, }; use std::ffi::OsStr; -use std::path::Path; use std::sync::mpsc::{channel, Receiver}; +use std::time::Instant; pub mod graphics; use graphics::GraphicsCommand; @@ -13,6 +13,7 @@ mod typescript; use typescript::transpile_to_javascript; mod io; +mod time; struct Loader {} @@ -25,8 +26,8 @@ impl Loader { impl ModuleLoader for Loader { fn load(&self, _context: &ContextRef, name: &str) -> Result { eprintln!("Loading {name}..."); - let path = Path::new(name); - let contents = std::fs::read_to_string(path)?; + let path = io::resolve_path(name, &["ts"])?; + let contents = std::fs::read_to_string(&path)?; let contents = if path.extension().and_then(OsStr::to_str) == Some("ts") { transpile_to_javascript(name, contents)? } else { @@ -45,6 +46,8 @@ pub struct ScriptContext { gfx: graphics::GraphicsAPI, gfx_receive: Receiver, + + time: time::TimeAPI, } impl ScriptContext { @@ -61,9 +64,10 @@ impl ScriptContext { let gfx = graphics::GraphicsAPI::define(&context, gfx_send.clone()) .expect("Graphics module should load without error"); let _io = io::IoAPI::define(&context).expect("IO module should load without error"); + let time = time::TimeAPI::define(&context).expect("Time module should load without error"); let module = context - .import_module("./src/main.ts", "") + .import_module("./main.ts", "") .expect("Unable to load main"); let init = module @@ -85,6 +89,8 @@ impl ScriptContext { gfx, gfx_receive, + + time, } } @@ -99,6 +105,10 @@ impl ScriptContext { } pub fn update(&mut self) { + // Do we update the frame time before of after async completion? + // Hmmmmm. + self.time.set_frame_time(Instant::now()); + // Tell the runtime to process all pending "jobs". This includes // promise completions. self.context diff --git a/src/script/io.rs b/src/script/io.rs index bfda8333..d508c040 100644 --- a/src/script/io.rs +++ b/src/script/io.rs @@ -1,4 +1,6 @@ use oden_js::{module::native::NativeModuleBuilder, ContextRef, ValueResult}; +use std::ffi::{OsStr, OsString}; +use std::path::PathBuf; use std::sync::mpsc::{channel, Receiver, Sender}; use std::sync::{Arc, Mutex}; use std::thread; @@ -69,10 +71,9 @@ impl IoImpl { let path = path.to_string(); self.thread_pool.execute(Box::new(move || { - // TODO: Actually read the path. let path = path; - let result = std::fs::read(path); + let result = resolve_path(&path, &[]).and_then(|p| std::fs::read(p)); promise.resolve(move |ctx: &ContextRef| match result { Ok(v) => ctx.new_array_buffer(v), Err(err) => Err(err.into()), @@ -100,3 +101,72 @@ impl IoAPI { Ok(IoAPI {}) } } + +fn append_ext(ext: impl AsRef, path: PathBuf) -> PathBuf { + let mut os_string: OsString = path.into(); + os_string.push("."); + os_string.push(ext.as_ref()); + os_string.into() +} + +/// Resolve a path to an existing file. +pub fn resolve_path(input: &str, extensions: &[&str]) -> std::io::Result { + let mut path = PathBuf::with_capacity(input.len()); + for segment in input.split('/') { + if segment == "." { + continue; + } else if segment == ".." { + if path.parent().is_none() { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!("{input} contains too many '..'; directory underflow detected"), + )); + } + + path.pop(); + } else if segment.contains(":") || segment.contains('\\') { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!("{input} contains invalid characters"), + )); + } else { + path.push(segment); + } + } + + if path.has_root() { + return Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("{input} is absolute"), + )); + } + if path.file_name().is_none() { + return Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("{input} refers to a directory"), + )); + } + + for prefix in ["game", "src"] { + let mut actual = PathBuf::from(prefix); + actual.push(path.clone()); + //eprintln!("Trying '{}'...", actual.to_string_lossy()); + match actual.try_exists() { + Ok(true) => return Ok(actual), + _ => (), + } + for extension in extensions.iter() { + let actual = append_ext(extension, actual.clone()); + //eprintln!("Trying '{}'...", actual.to_string_lossy()); + match actual.try_exists() { + Ok(true) => return Ok(actual), + _ => (), + } + } + } + + return Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("{input} not found or not accessible"), + )); +} diff --git a/src/script/time.rs b/src/script/time.rs new file mode 100644 index 00000000..95e67131 --- /dev/null +++ b/src/script/time.rs @@ -0,0 +1,65 @@ +use oden_js::{module::native::NativeModuleBuilder, ContextRef}; +use std::cell::RefCell; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +struct TimeImpl { + start: Instant, + last_frame_time: Instant, + frame_time: Instant, +} + +impl TimeImpl { + fn new() -> Self { + let now = Instant::now(); + TimeImpl { + start: now, + frame_time: now, + last_frame_time: now - Duration::from_millis(15), + } + } + + fn since_last_frame(&self) -> f64 { + (self.frame_time - self.last_frame_time).as_secs_f64() + } + + fn since_start(&self) -> f64 { + (self.frame_time - self.start).as_secs_f64() + } + + fn set_frame_time(&mut self, now: Instant) { + self.last_frame_time = self.frame_time; + self.frame_time = now; + } +} + +pub struct TimeAPI { + time: Arc>, +} + +impl TimeAPI { + pub fn define(ctx: &ContextRef) -> oden_js::Result { + let time = Arc::new(RefCell::new(TimeImpl::new())); + let mut builder = NativeModuleBuilder::new(ctx); + { + let time = time.clone(); + builder.export( + "since_last_frame", + ctx.new_fn(move |_ctx: &ContextRef| time.borrow().since_last_frame())?, + )?; + } + { + let time = time.clone(); + builder.export( + "since_start", + ctx.new_fn(move |_ctx: &ContextRef| time.borrow().since_start())?, + )?; + } + builder.build("time-core")?; + Ok(TimeAPI { time }) + } + + pub fn set_frame_time(&mut self, now: Instant) { + self.time.borrow_mut().set_frame_time(now); + } +} diff --git a/src/time.ts b/src/time.ts new file mode 100644 index 00000000..0e12209e --- /dev/null +++ b/src/time.ts @@ -0,0 +1,13 @@ +import * as time from "time-core"; + +/** + * Get the time elasped since the start of the program. + * @returns The time since the start of the program, in fractional seconds. + */ +export const since_start = time.since_start; + +/** + * Get the time elasped since the last frame. + * @returns The time since the last frame, in fractional seconds. + */ +export const since_last_frame = time.since_last_frame; diff --git a/tsconfig.json b/tsconfig.json index 776c55ce..2bd41255 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,8 @@ { - "include": ["src/**/*"], + "include": [ + "src/**/*", + "game/**/*", + ], "compilerOptions": { /* Visit https://aka.ms/tsconfig to read more about this file */ @@ -29,16 +32,19 @@ /* Modules */ "module": "ES2020", /* Specify what module code is generated. */ - // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + "moduleResolution": "nodenext", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + "rootDirs": [ /* Allow multiple folders to be treated as one when resolving modules. */ + "./src", + "./game", + ], "typeRoots": [ /* Specify multiple folders that act like './node_modules/@types'. */ "./types", ], // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "moduleSuffixes": ["ts"], /* List of file name suffixes to search when resolving a module. */ "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ diff --git a/types/time-core.d.ts b/types/time-core.d.ts new file mode 100644 index 00000000..fc64da9a --- /dev/null +++ b/types/time-core.d.ts @@ -0,0 +1,4 @@ +// These are the functions exposed by the native time module. +// +export function since_start(): number; +export function since_last_frame(): number;