[oden] Time, path searching, game directory
This commit is contained in:
parent
96e95e22ce
commit
26bfcc7a94
8 changed files with 185 additions and 13 deletions
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
|
@ -1,5 +1,6 @@
|
||||||
import { cls, print, spr, use_texture } from "./graphics.ts";
|
import { cls, print, spr, use_texture } from "./graphics";
|
||||||
import { load_texture } from "./assets.ts";
|
import { load_texture } from "./assets";
|
||||||
|
import { since_start, since_last_frame } from "./time";
|
||||||
|
|
||||||
let the_texture: number | undefined = undefined;
|
let the_texture: number | undefined = undefined;
|
||||||
|
|
||||||
|
|
@ -7,7 +8,10 @@ export function init() {
|
||||||
print("Hello world!");
|
print("Hello world!");
|
||||||
|
|
||||||
// Start this load, but then...
|
// 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() {}
|
export function update() {}
|
||||||
|
|
@ -3,8 +3,8 @@ use oden_js::{
|
||||||
Context, ContextRef, Result, Runtime, Value,
|
Context, ContextRef, Result, Runtime, Value,
|
||||||
};
|
};
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::path::Path;
|
|
||||||
use std::sync::mpsc::{channel, Receiver};
|
use std::sync::mpsc::{channel, Receiver};
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
pub mod graphics;
|
pub mod graphics;
|
||||||
use graphics::GraphicsCommand;
|
use graphics::GraphicsCommand;
|
||||||
|
|
@ -13,6 +13,7 @@ mod typescript;
|
||||||
use typescript::transpile_to_javascript;
|
use typescript::transpile_to_javascript;
|
||||||
|
|
||||||
mod io;
|
mod io;
|
||||||
|
mod time;
|
||||||
|
|
||||||
struct Loader {}
|
struct Loader {}
|
||||||
|
|
||||||
|
|
@ -25,8 +26,8 @@ impl Loader {
|
||||||
impl ModuleLoader for Loader {
|
impl ModuleLoader for Loader {
|
||||||
fn load(&self, _context: &ContextRef, name: &str) -> Result<ModuleSource> {
|
fn load(&self, _context: &ContextRef, name: &str) -> Result<ModuleSource> {
|
||||||
eprintln!("Loading {name}...");
|
eprintln!("Loading {name}...");
|
||||||
let path = Path::new(name);
|
let path = io::resolve_path(name, &["ts"])?;
|
||||||
let contents = std::fs::read_to_string(path)?;
|
let contents = std::fs::read_to_string(&path)?;
|
||||||
let contents = if path.extension().and_then(OsStr::to_str) == Some("ts") {
|
let contents = if path.extension().and_then(OsStr::to_str) == Some("ts") {
|
||||||
transpile_to_javascript(name, contents)?
|
transpile_to_javascript(name, contents)?
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -45,6 +46,8 @@ pub struct ScriptContext {
|
||||||
|
|
||||||
gfx: graphics::GraphicsAPI,
|
gfx: graphics::GraphicsAPI,
|
||||||
gfx_receive: Receiver<graphics::GraphicsCommand>,
|
gfx_receive: Receiver<graphics::GraphicsCommand>,
|
||||||
|
|
||||||
|
time: time::TimeAPI,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScriptContext {
|
impl ScriptContext {
|
||||||
|
|
@ -61,9 +64,10 @@ impl ScriptContext {
|
||||||
let gfx = graphics::GraphicsAPI::define(&context, gfx_send.clone())
|
let gfx = graphics::GraphicsAPI::define(&context, gfx_send.clone())
|
||||||
.expect("Graphics module should load without error");
|
.expect("Graphics module should load without error");
|
||||||
let _io = io::IoAPI::define(&context).expect("IO 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
|
let module = context
|
||||||
.import_module("./src/main.ts", "")
|
.import_module("./main.ts", "")
|
||||||
.expect("Unable to load main");
|
.expect("Unable to load main");
|
||||||
|
|
||||||
let init = module
|
let init = module
|
||||||
|
|
@ -85,6 +89,8 @@ impl ScriptContext {
|
||||||
|
|
||||||
gfx,
|
gfx,
|
||||||
gfx_receive,
|
gfx_receive,
|
||||||
|
|
||||||
|
time,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,6 +105,10 @@ impl ScriptContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self) {
|
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
|
// Tell the runtime to process all pending "jobs". This includes
|
||||||
// promise completions.
|
// promise completions.
|
||||||
self.context
|
self.context
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
use oden_js::{module::native::NativeModuleBuilder, ContextRef, ValueResult};
|
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::mpsc::{channel, Receiver, Sender};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
@ -69,10 +71,9 @@ impl IoImpl {
|
||||||
|
|
||||||
let path = path.to_string();
|
let path = path.to_string();
|
||||||
self.thread_pool.execute(Box::new(move || {
|
self.thread_pool.execute(Box::new(move || {
|
||||||
// TODO: Actually read the path.
|
|
||||||
let path = 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 {
|
promise.resolve(move |ctx: &ContextRef| match result {
|
||||||
Ok(v) => ctx.new_array_buffer(v),
|
Ok(v) => ctx.new_array_buffer(v),
|
||||||
Err(err) => Err(err.into()),
|
Err(err) => Err(err.into()),
|
||||||
|
|
@ -100,3 +101,72 @@ impl IoAPI {
|
||||||
Ok(IoAPI {})
|
Ok(IoAPI {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn append_ext(ext: impl AsRef<OsStr>, 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<PathBuf> {
|
||||||
|
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"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
|
||||||
65
src/script/time.rs
Normal file
65
src/script/time.rs
Normal file
|
|
@ -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<RefCell<TimeImpl>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimeAPI {
|
||||||
|
pub fn define(ctx: &ContextRef) -> oden_js::Result<Self> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/time.ts
Normal file
13
src/time.ts
Normal file
|
|
@ -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;
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
{
|
{
|
||||||
"include": ["src/**/*"],
|
"include": [
|
||||||
|
"src/**/*",
|
||||||
|
"game/**/*",
|
||||||
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||||
|
|
||||||
|
|
@ -29,16 +32,19 @@
|
||||||
|
|
||||||
/* Modules */
|
/* Modules */
|
||||||
"module": "ES2020", /* Specify what module code is generated. */
|
"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. */
|
// "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. */
|
// "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'. */
|
"typeRoots": [ /* Specify multiple folders that act like './node_modules/@types'. */
|
||||||
"./types",
|
"./types",
|
||||||
],
|
],
|
||||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
// "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. */
|
"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. */
|
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||||
|
|
|
||||||
4
types/time-core.d.ts
vendored
Normal file
4
types/time-core.d.ts
vendored
Normal file
|
|
@ -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;
|
||||||
Loading…
Add table
Add a link
Reference in a new issue