Compare commits

...

7 commits

16 changed files with 181 additions and 107 deletions

View file

@ -324,6 +324,34 @@ impl ContextRef {
}
}
unsafe extern "C" fn free_array_buffer_bytes(
_rt: *mut sys::JSRuntime,
opaque: *mut std::ffi::c_void,
_ptr: *mut std::ffi::c_void,
) {
drop(Box::from_raw(opaque as *mut Vec<u8>));
}
pub fn new_array_buffer<T>(&self, buffer: T) -> ValueResult
where
T: Into<Vec<u8>>,
{
let mut vec_box = Box::new(buffer.into());
unsafe {
let is_shared = 0;
let byte_ptr = vec_box.as_mut_ptr();
let byte_len = vec_box.len();
self.check_exception(sys::JS_NewArrayBuffer(
self.ctx,
byte_ptr,
byte_len,
Some(Self::free_array_buffer_bytes),
Box::into_raw(vec_box) as *mut _,
is_shared,
))
}
}
/// Fetch the global object for the context.
pub fn global_object(&self) -> ValueResult {
self.check_exception(unsafe { sys::JS_GetGlobalObject(self.ctx) })

View file

@ -12,7 +12,7 @@ impl PromiseHandle {
}
}
pub type PromiseResult = Box<dyn Fn(&ContextRef) -> ValueResult + Send + 'static>;
pub type PromiseResult = Box<dyn FnOnce(&ContextRef) -> ValueResult + Send + 'static>;
pub(crate) enum PromiseEvent {
Resolved(PromiseResult),
@ -48,7 +48,7 @@ impl Promise {
pub fn resolve<T>(mut self, value: T)
where
T: Fn(&ContextRef) -> ValueResult + Send + 'static,
T: FnOnce(&ContextRef) -> ValueResult + Send + 'static,
{
let _ = self
.channel
@ -58,7 +58,7 @@ impl Promise {
pub fn reject<T>(mut self, value: T)
where
T: Fn(&ContextRef) -> ValueResult + Send + 'static,
T: FnOnce(&ContextRef) -> ValueResult + Send + 'static,
{
let _ = self
.channel

View file

@ -175,37 +175,44 @@ impl Runtime {
/// Process all pending async jobs. This includes all promise resolutions.
fn process_promise_completions(&self) {
// TODO: This could be more robust if we buffered all the completed
// promise entries and then dropped the borrow of the state, so
// that we never invoked user code while borrowing our private
// state mutably.
let mut promises = vec![];
// First, gather all the completed promises into a temporary vector
// so that we only need to borrow our mutable state for a short
// period of time.
let mut state = unsafe { PrivateState::from_rt_mut(self.rt) };
while let Ok((handle, evt)) = state.promise_recv.try_recv() {
if let Some(entry) = state.promise_table.remove(&handle) {
let ctx = ContextRef::from_raw(entry.context);
let (callback, value) = match evt {
PromiseEvent::Resolved(v) => (entry.resolve, v),
PromiseEvent::Rejected(v) => (entry.reject, v),
};
// Convert the result into a JS value, which we can only
// really do while we are on this thread.
let value = value(&ctx).expect("Should be able to convert promise result to value");
// Call the particular callback and make sure it doesn't throw.
ctx.check_exception(unsafe {
let mut args = [value.val];
sys::JS_Call(
entry.context,
callback,
sys::JS_MakeUndefined(),
1,
args.as_mut_ptr(),
)
})
.expect("Exception thrown by promise callback");
promises.push((entry, evt));
}
}
drop(state); // Don't need our internal state anymore.
// Nowe we can complete all the promises.
for (entry, evt) in promises {
let ctx = ContextRef::from_raw(entry.context);
let (callback, value) = match evt {
PromiseEvent::Resolved(v) => (entry.resolve, v),
PromiseEvent::Rejected(v) => (entry.reject, v),
};
// Convert the result into a JS value, which we can only
// really do while we are on this thread.
let value = value(&ctx).expect("Should be able to convert promise result to value");
// Call the particular callback and make sure it doesn't throw.
ctx.check_exception(unsafe {
let mut args = [value.val];
sys::JS_Call(
entry.context,
callback,
sys::JS_MakeUndefined(),
1,
args.as_mut_ptr(),
)
})
.expect("Exception thrown by promise callback");
}
}
/// Process all pending async jobs. This includes all promise resolutions.

View file

@ -323,6 +323,20 @@ impl ValueRef {
))
}
}
/// Get the underlying bytes for a an ArrayBuffer object. Obviously it
/// only works if this is an un-detached ArrayBuffer value.
pub fn get_array_buffer<'a>(&'a self, ctx: &ContextRef) -> Result<&'a [u8]> {
unsafe {
let mut size: usize = 0;
let buffer = sys::JS_GetArrayBuffer(ctx.ctx, &mut size as *mut usize, self.val);
if buffer.is_null() {
Err(ctx.exception_error())
} else {
Ok(std::slice::from_raw_parts(buffer, size))
}
}
}
}
impl fmt::Debug for ValueRef {

View file

@ -1,5 +1,7 @@
import * as core from "asset-core";
import * as io from "./io.ts";
import * as gfx from "./graphics.ts";
export function load_texture(path: string): number {
return core.load_texture(path);
export async function load_texture(path: string): Promise<number> {
const buffer = await io.load(path);
return gfx.create_texture(buffer, path);
}

View file

@ -52,6 +52,19 @@ export function spr(
core.spr(x, y, w, h, sx, sy, sw, sh);
}
/**
* Create a texture based on the loaded buffer.
*
* @param buffer The underlying bytes that make up the texture image.
* @param label The label to put onto the texture (for debugging).
*/
export function create_texture(
buffer: ArrayBuffer,
label: string | undefined = undefined
): number {
return core.create_texture(buffer, label);
}
/**
* Set the specified texture as the current texture for calls to e.g. spr().
*

11
src/io.ts Normal file
View file

@ -0,0 +1,11 @@
import * as core from "io-core";
/**
* Load the specified file into memory.
*
* @param path The path of the file to load.
* @returns The contents of the file.
*/
export function load(path: string): Promise<ArrayBuffer> {
return core.load(path);
}

View file

@ -339,7 +339,10 @@ impl State {
&self.device,
&self.queue,
&ct.image,
Some(&ct.label),
match &ct.label {
Some(l) => Some(&l),
None => None,
},
);
let sprite_bind_group =
self.device.create_bind_group(&wgpu::BindGroupDescriptor {
@ -354,7 +357,10 @@ impl State {
resource: wgpu::BindingResource::Sampler(&texture.sampler),
},
],
label: Some(&ct.label),
label: match &ct.label {
Some(l) => Some(&l),
None => None,
},
});
self.sprite_textures.insert(ct.id, sprite_bind_group);

View file

@ -1,18 +1,22 @@
import { cls, print, spr, use_texture } from "./graphics.ts";
import { load_texture } from "./assets.ts";
let the_texture = 0;
let the_texture: number | undefined = undefined;
export function init() {
print("Hello world!");
// TODO: Async IO
the_texture = load_texture("./src/happy-tree.png");
// Start this load, but then...
load_texture("./src/happy-tree.png").then((n) => (the_texture = n));
}
export function update() {}
export function draw() {
cls(0.1, 0.2, 0.3);
use_texture(the_texture);
spr((320 - 256) / 2, 0, 256, 240, 0, 0);
if (the_texture != undefined) {
// ...it gets resolved here?
use_texture(the_texture);
spr((320 - 256) / 2, 0, 256, 240, 0, 0);
}
}

View file

@ -12,8 +12,7 @@ use graphics::GraphicsCommand;
mod typescript;
use typescript::transpile_to_javascript;
pub mod assets;
pub mod io;
mod io;
struct Loader {}
@ -45,7 +44,6 @@ pub struct ScriptContext {
draw: Value,
gfx: graphics::GraphicsAPI,
_assets: assets::AssetsAPI,
gfx_receive: Receiver<graphics::GraphicsCommand>,
}
@ -62,8 +60,6 @@ impl ScriptContext {
let gfx = graphics::GraphicsAPI::define(&context, gfx_send.clone())
.expect("Graphics module should load without error");
let assets = assets::AssetsAPI::define(&context, gfx_send.clone())
.expect("Assets module should load without error");
let _io = io::IoAPI::define(&context).expect("IO module should load without error");
let module = context
@ -89,8 +85,6 @@ impl ScriptContext {
gfx,
gfx_receive,
_assets: assets,
}
}

View file

@ -1,56 +0,0 @@
use crate::script::graphics::{CreateTextureCommand, GraphicsCommand};
use oden_js::{module::native::NativeModuleBuilder, ContextRef, Error, Result};
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::mpsc::Sender;
use std::sync::Arc;
struct AssetsImpl {
next_texture_id: AtomicU32,
gfx_sender: Sender<GraphicsCommand>,
}
impl AssetsImpl {
fn new(sender: Sender<GraphicsCommand>) -> Self {
AssetsImpl {
gfx_sender: sender,
next_texture_id: AtomicU32::new(0),
}
}
fn load_texture(&self, path: &str) -> Result<u32> {
let bytes = std::fs::read(path)?;
let image = match image::load_from_memory(&bytes) {
Ok(i) => i,
Err(e) => return Err(Error::RustFunctionError(format!("{e}"))),
};
let id = self.next_texture_id.fetch_add(1, Ordering::SeqCst);
let _ = self
.gfx_sender
.send(GraphicsCommand::CreateTexture(CreateTextureCommand {
id,
image,
label: path.into(),
}));
Ok(id)
}
}
pub struct AssetsAPI {}
impl AssetsAPI {
pub fn define(ctx: &ContextRef, sender: Sender<GraphicsCommand>) -> oden_js::Result<Self> {
let assets = Arc::new(AssetsImpl::new(sender));
let mut builder = NativeModuleBuilder::new(ctx);
{
let assets = assets.clone();
builder.export(
"load_texture",
ctx.new_fn(move |_ctx: &ContextRef, p: String| assets.load_texture(&p))?,
)?;
}
builder.build("asset-core")?;
Ok(AssetsAPI {})
}
}

View file

@ -1,4 +1,5 @@
use oden_js::{module, ContextRef};
use oden_js::{module, ContextRef, Error, Result, Value};
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::mpsc::Sender;
use std::sync::Arc;
@ -28,7 +29,7 @@ pub struct SpriteCommand {
pub struct CreateTextureCommand {
pub id: u32,
pub image: image::DynamicImage,
pub label: String,
pub label: Option<String>,
}
#[derive(Debug)]
@ -42,12 +43,16 @@ pub enum GraphicsCommand {
}
struct GraphicsImpl {
next_texture_id: AtomicU32,
sender: Sender<GraphicsCommand>,
}
impl GraphicsImpl {
pub fn new(sender: Sender<GraphicsCommand>) -> Self {
GraphicsImpl { sender }
GraphicsImpl {
sender,
next_texture_id: AtomicU32::new(0),
}
}
fn print(&self, text: String) -> () {
@ -75,6 +80,30 @@ impl GraphicsImpl {
}));
}
fn create_texture(
&self,
context: &ContextRef,
buffer: Value,
label: Option<String>,
) -> Result<u32> {
let bytes = buffer.get_array_buffer(context)?;
let image = match image::load_from_memory(&bytes) {
Ok(i) => i,
Err(e) => return Err(Error::RustFunctionError(format!("{e}"))),
};
let id = self.next_texture_id.fetch_add(1, Ordering::SeqCst);
let _ = self
.sender
.send(GraphicsCommand::CreateTexture(CreateTextureCommand {
id,
image,
label,
}));
Ok(id)
}
fn use_texture(&self, id: u32) {
let _ = self.sender.send(GraphicsCommand::UseTexture(id));
}
@ -126,6 +155,17 @@ impl GraphicsAPI {
ctx.new_fn(move |_: &ContextRef, id: u32| gfx.use_texture(id))?,
)?;
}
{
let gfx = gfx.clone();
builder.export(
"create_texture",
ctx.new_fn(
move |c: &ContextRef, buffer: Value, label: Option<String>| {
gfx.create_texture(c, buffer, label)
},
)?,
)?;
}
builder.build("graphics-core")?;
Ok(GraphicsAPI { gfx })
}

View file

@ -71,7 +71,12 @@ impl IoImpl {
self.thread_pool.execute(Box::new(move || {
// TODO: Actually read the path.
let path = path;
promise.resolve(move |ctx: &ContextRef| ctx.new_string(&path));
let result = std::fs::read(path);
promise.resolve(move |ctx: &ContextRef| match result {
Ok(v) => ctx.new_array_buffer(v),
Err(err) => Err(err.into()),
});
}));
Ok(value)

View file

@ -1 +0,0 @@
export function load_texture(path: string): number;

View file

@ -12,4 +12,8 @@ export function spr(
sw: number,
sh: number
);
export function create_texture(
buffer: ArrayBuffer,
label: string | undefined
): number;
export function use_texture(id: number);

3
types/io-core.d.ts vendored Normal file
View file

@ -0,0 +1,3 @@
// These are the functions exposed by the native IO module.
//
export function load(path: string): Promise<ArrayBuffer>;