Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(runtime): Implement effect queue #57

Merged
merged 6 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions examples/hello/hello.fs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ type Msg =

let game: Game<Model, Msg> = GameBuilder.local Model.initial

let update model msg =
printfn "Running update"
(model, Effect.none())

let tick model (tick: Time.FrameTime) =

let applyVelocity (tick: Time.FrameTime) ball =
Expand Down Expand Up @@ -111,5 +115,6 @@ let init (_args: array<string>) =
|> Transform.translateZ ((sin (frameTime.tts * 5.0f)) * 1.0f)
|])
)
|> GameBuilder.update update
|> GameBuilder.tick tick
|> Runtime.runGame
16 changes: 15 additions & 1 deletion runtime/functor-runtime-common/src/effect.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use fable_library_rust::Native_::Func1;
use fable_library_rust::{NativeArray_, Native_::Func1};

#[derive(Clone)]
pub enum Effect<T: Clone + 'static> {
Expand All @@ -7,6 +7,13 @@ pub enum Effect<T: Clone + 'static> {
}

impl<T: Clone + 'static> Effect<T> {
pub fn is_none(effect: &Effect<T>) -> bool {
match effect {
Effect::None => true,
_ => false,
}
}

pub fn none() -> Effect<T> {
Effect::None
}
Expand All @@ -21,4 +28,11 @@ impl<T: Clone + 'static> Effect<T> {
Effect::Wrapped(v) => Effect::Wrapped(mapping(v)),
}
}

pub fn run(effect: Effect<T>) -> NativeArray_::Array<T> {
match effect {
Effect::None => NativeArray_::array_from(vec![]),
Effect::Wrapped(v) => NativeArray_::array_from(vec![v]),
}
}
}
38 changes: 38 additions & 0 deletions runtime/functor-runtime-common/src/effect_queue.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use std::{cell::RefCell, collections::VecDeque, fmt, sync::Arc};

use crate::Effect;

#[derive(Clone)]
pub struct EffectQueue<T: Clone + 'static> {
queue: Arc<RefCell<VecDeque<Effect<T>>>>,
}

impl<T: Clone + 'static> EffectQueue<T> {
pub fn new() -> EffectQueue<T> {
EffectQueue {
queue: Arc::new(RefCell::new(VecDeque::new())),
}
}

pub fn count(effect_queue: &EffectQueue<T>) -> i32 {
effect_queue.queue.borrow().len() as i32
}

pub fn enqueue(effect_queue: &EffectQueue<T>, effect: Effect<T>) {
if !Effect::is_none(&effect) {
effect_queue.queue.borrow_mut().push_front(effect);
}
}

pub fn dequeue(effect_queue: &EffectQueue<T>) -> Option<Effect<T>> {
effect_queue.queue.borrow_mut().pop_back()
}
}

// Implement Debug manually
impl<T: Clone + 'static> fmt::Debug for EffectQueue<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Build the debug representation
f.debug_struct("EffectQueue").finish()
}
}
2 changes: 2 additions & 0 deletions runtime/functor-runtime-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ impl OpaqueState {
pub mod animation;
pub mod asset;
mod effect;
mod effect_queue;
mod frame_time;
pub mod geometry;
pub mod io;
Expand All @@ -65,6 +66,7 @@ mod shader_program;
pub mod texture;

pub use effect::*;
pub use effect_queue::*;
pub use frame_time::*;
pub use render_context::*;
pub use scene3d::*;
Expand Down
2 changes: 2 additions & 0 deletions runtime/functor-runtime-desktop/src/game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use functor_runtime_common::{FrameTime, Scene3D};
pub trait Game {
fn check_hot_reload(&mut self, frame_time: FrameTime);

fn tick(&mut self, frame_time: FrameTime);

fn render(&mut self, frame_time: FrameTime) -> Scene3D;

fn quit(&mut self);
Expand Down
8 changes: 8 additions & 0 deletions runtime/functor-runtime-desktop/src/hot_reload_game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ impl Game for HotReloadGame {
}
}

fn tick(&mut self, frame_time: FrameTime) {
unsafe {
let tick_func: Symbol<fn(FrameTime)> =
self.library.as_ref().unwrap().get(b"tick").unwrap();
tick_func(frame_time)
}
}

fn quit(&mut self) {
if let Some(handle) = self.watcher_thread.take() {
handle.join().expect("Failed to join watcher thread");
Expand Down
2 changes: 2 additions & 0 deletions runtime/functor-runtime-desktop/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ pub async fn main() {
}
}

game.tick(time.clone());

gl.clear(glow::COLOR_BUFFER_BIT | glow::DEPTH_BUFFER_BIT);
let radius = 5.0;
let view_matrix: Matrix4<f32> = Matrix4::look_at_rh(
Expand Down
7 changes: 7 additions & 0 deletions runtime/functor-runtime-desktop/src/static_game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ impl Game for StaticGame {
}
}

fn tick(&mut self, frame_time: FrameTime) {
unsafe {
let tick_func: Symbol<fn(FrameTime)> = self.library.get(b"tick").unwrap();
tick_func(frame_time)
}
}

fn quit(&mut self) {
// Noop - nothing to do yet
}
Expand Down
6 changes: 5 additions & 1 deletion runtime/functor-runtime-web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<canvas id="canvas" width="640" height="480"></canvas>
<script type="module">

import init1, { test_render_wasm } from "./build-wasm/pkg/game_wasm.js";
import init1, { test_render_wasm, tick_wasm } from "./build-wasm/pkg/game_wasm.js";
import startRuntime from "./pkg/functor_runtime_web.js";

const initOutput = init1();
Expand All @@ -19,6 +19,10 @@
return test_render_wasm(frameTime);
};

window.game.tick = (frameTime) => {
return tick_wasm(frameTime);
}

startRuntime();
});
</script>
Expand Down
5 changes: 5 additions & 0 deletions runtime/functor-runtime-web/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ fn request_animation_frame(f: &Closure<dyn FnMut()>) {
extern "C" {
#[wasm_bindgen(js_namespace = game, js_name = render)]
fn game_render(frameTimeJs: JsValue) -> JsValue;

#[wasm_bindgen(js_namespace = game, js_name = tick)]
fn game_tick(frameTimeJs: JsValue);
}

#[wasm_bindgen(start)]
Expand Down Expand Up @@ -206,6 +209,8 @@ async fn run_async() -> Result<(), JsValue> {

// let scene = Scene3D::cube();

game_tick(functor_runtime_common::to_js_value(&frame_time));

let val = game_render(functor_runtime_common::to_js_value(&frame_time));
web_sys::console::log_2(&JsValue::from_str("calling render"), &val);

Expand Down
8 changes: 7 additions & 1 deletion src/Functor.Game/Effect.fs
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,10 @@ module Effect =
let wrapped (a: 'a) : effect<'a> = nativeOnly

[<Emit("functor_runtime_common::Effect::map($0, $1)")>]
let map (fn: 'a -> 'b) (eff: effect<'a>) : effect<'b> = nativeOnly
let map (fn: 'a -> 'b) (eff: effect<'a>) : effect<'b> = nativeOnly


// TODO: These should live elsewhere because they aren't user space

[<Emit("functor_runtime_common::Effect::run($0)")>]
let run (eff: effect<'a>) : 'a array = nativeOnly
19 changes: 19 additions & 0 deletions src/Functor.Game/EffectQueue.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace Functor
open Fable.Core

[<Erase; Emit("functor_runtime_common::EffectQueue<$0>")>] type EffectQueue<'msg> = | Noop


module EffectQueue =

[<Emit("functor_runtime_common::EffectQueue::new()")>]
let empty (): EffectQueue<_> = nativeOnly

[<Emit("functor_runtime_common::EffectQueue::count(&$0)")>]
let count (effectQueue: EffectQueue<'a>): int = nativeOnly

[<Emit("functor_runtime_common::EffectQueue::enqueue(&$1, $0)")>]
let enqueue (eff: effect<'a>) (effectQueue: EffectQueue<'a>) : unit = nativeOnly

[<Emit("functor_runtime_common::EffectQueue::dequeue(&$0)")>]
let dequeue (effectQueue: EffectQueue<'a>): Option<effect<'a>> = nativeOnly
1 change: 1 addition & 0 deletions src/Functor.Game/Functor.Game.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="Effect.fs" />
<Compile Include="EffectQueue.fs" />
<Compile Include="Math/Angle.fs" />
<Compile Include="Math/Vector2.fs" />
<Compile Include="Math/Point2.fs" />
Expand Down
4 changes: 4 additions & 0 deletions src/Functor.Game/Game.fs
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,9 @@ module GameRunner =
let (newModel, effect) = game.tick model tick
(newModel, effect)

let update<'model, 'msg> (game: Game<'model, 'msg>) (model: 'model) (msg: 'msg) =
let (newModel, effect) = game.update model msg
(newModel, effect)

let draw3d<'model, 'msg> (game: Game<'model, 'msg>) (model: 'model) (tick: Time.FrameTime) =
game.draw3d model tick
1 change: 1 addition & 0 deletions src/Functor.Game/Game.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ module GameBuilder =
module GameRunner =
val initialState: Game<'model, 'msg> -> 'model
val tick: Game<'model, 'msg> -> 'model -> Time.FrameTime -> ('model * effect<'msg>)
val update: Game<'model, 'msg> -> 'model -> 'msg -> ('model * effect<'msg>)
val draw3d: Game<'model, 'msg> -> 'model -> Time.FrameTime -> Graphics.Scene3D
33 changes: 32 additions & 1 deletion src/Functor.Game/Runtime.fs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module Runtime
let mutable currentRunner: Option<IRunner> = None

open Fable.Core.Rust
open System.Collections.Generic

///////////////////////////////
// WebAssembly API
Expand All @@ -36,6 +37,15 @@ module Runtime
let from_js<'a>(obj: JsValue): 'a = nativeOnly


[<OuterAttr("wasm_bindgen")>]
let tick_wasm (frameTimeJs: JsValue): unit =
let frameTime = frameTimeJs |> UnsafeJsValue.from_js<Time.FrameTime>;
if currentRunner.IsSome then
frameTime
|> currentRunner.Value.tick
else
raise (System.Exception("No runner"))

[<OuterAttr("wasm_bindgen")>]
let test_render_wasm (frameTimeJs: JsValue): JsValue =
let frameTime = frameTimeJs |> UnsafeJsValue.from_js<Time.FrameTime>;
Expand Down Expand Up @@ -87,6 +97,7 @@ module Runtime
type GameExecutor<'Msg, 'Model>(game: Game<'Model, 'Msg>, initialState: 'Model) =
let myGame = game
let mutable state: 'Model = initialState
let mutable effectQueue: EffectQueue<'Msg> = EffectQueue.empty()
do
printfn "Hello from GameRunner!"
interface IRunner with
Expand All @@ -98,9 +109,29 @@ module Runtime

// Todo: If first frame, run 'init'

printfn "Effect count: %d" (EffectQueue.count effectQueue)
// Todo: Run any pending effects
// Print the number of pending effects in the queue

let maybe_effect = EffectQueue.dequeue effectQueue;

let finalState =
match maybe_effect with
| None ->
printfn "No effect"
state
| Some _e ->
let arr: 'Msg array = Effect.run _e

Array.fold (fun (currentState) msg ->
let (newState, effect) = GameRunner.update myGame state msg
EffectQueue.enqueue effect effectQueue
newState
) (state) arr

let (newState, effect) = GameRunner.tick myGame finalState frameTime

let (newState, effects) = GameRunner.tick myGame state frameTime
EffectQueue.enqueue effect effectQueue;

// Todo: Run tick effects

Expand Down
Loading