Skip to content
This repository has been archived by the owner on Feb 7, 2024. It is now read-only.

Pixi: fail to use renderer #1

Closed
whitetigle opened this issue Aug 23, 2016 · 94 comments
Closed

Pixi: fail to use renderer #1

whitetigle opened this issue Aug 23, 2016 · 94 comments

Comments

@whitetigle
Copy link

whitetigle commented Aug 23, 2016

Hi!
I am trying to setup a very simple Pixi project. However I fail right at the start with a compilation error.
Since I am new to both Fable & F# there may be things I do wrong. Or maybe I did not understand the Erase attribute sample in the doc since Globals.autodetectRenderer returns a U2<WebGLRenderer, CanvasRenderer>

Anyway here is my code :

open System
open Fable.Core
open Fable.Core.JsInterop
open Fable.Import.PIXI
open Fable.Import.Browser

let currentRenderer =
  let renderer = Globals.autoDetectRenderer( 800., 600.  )
  match renderer with
    | U2.Case1 webgl -> webgl :> SystemRenderer
    | U2.Case2 canvas -> canvas :> SystemRenderer

document.body.appendChild( currentRenderer.view )

When I compile it says :

fable-compiler 0.5.8: Start compilation...
ERROR: Unsupported type test: GenericParam "b" (L27,8-L27,16)

which corresponds to my renderer value

match renderer with

I use fable-core 0.5.5.
I have tried other ways but I can't get past this error.
Thanks for your kind help!

@alfonsogarciacaro
Copy link
Member

Hi there! The Pixi bindings aren't very tested so there may be some rough edges, sorry for that!

The erased union types are indeed erased in runtime that's why you cannot do type testing against them. I'm not sure if it'll solve the problem but probably the best thing is to cast the returned valued directly with unbox.

let currentRenderer =
  Globals.autoDetectRenderer( 800., 600.  )
  |> unbox<SystemRenderer>

@whitetigle
Copy link
Author

Ok that works and it's simpler. Thanks!

Regarding the Erased unions, I was following the example from https://fable-compiler.github.io/docs/interacting.html page:

open Fable.Core

type Test() =
    member x.Value = 'Test'


let myMethod (arg: U3<string, int, Test>) =
    match arg with
    | U3.Case1 s -> s
    | U3.Case2 i -> string i
    | U3.Case3 t -> t.Value

Now you gave me the answer, I just tried to compile this example and it gives the same error I got. I should have started with this and ask you to understand what was happening.

So to summarize we don't have to do the whole matching but we can just unbox to the desired type. Is that so?

Now, I have done quite a few projects using Pixi in another language (Haxe). I am actually trying to port one using Fable and F# so If I encounter other problems may I ping you from time to time?
If so is is better to use gitter for that or submit an issue?

Anyway thanks for your help!

@whitetigle
Copy link
Author

whitetigle commented Aug 24, 2016

I put my notes here while I am working (please tell me if you want to start another discussion elsewhere).

1. optional values VS default values

Let's take an example. Setting optional parameters for the renderer.

JS
var renderer = PIXI.autoDetectRenderer(800, 600, { antialias: true });

F#

type Options() =
  interface RendererOptions with
    member val antialias = Some(true) with get, set
    member val view = None with get, set
    member val transparent = None with get, set
    member val autoResize = None with get, set
    member val resolution = Some(1.) with get, set
    member val clearBeforeRendering = None with get, set
    member val preserveDrawingBuffer = None with get, set
    member val forceFXAA = None with get, set
    member val roundPixels = None with get, set
    member val backgroundColor = Some(float 0x000000) with get, set

let currentRenderer =
  Globals.autoDetectRenderer( 800., 600., Options() )
  |> unbox<SystemRenderer>

If I understand correctly interfaces in F# we must explicitely define all members. So setting a None value when the JS code is waiting for, let's say, a float value is certainly not what we want to do..

*So here is my question: is there a way to define default values instead or make options becomes REALLY optional (create the object in JS without the field) *

For instance, the resolution parameter of my Options object is 1.0 by default. But here since we use an interface, we must explicitely set Some(1.0) since None is not what we want.

In JS we just omit the parameter. In Haxe, we omit the field and it will create an anonymous object without the parameter. Can we achieve the same in F#? Am i right regarding the use of interfaces in F#?

Thanks

@whitetigle
Copy link
Author

whitetigle commented Aug 24, 2016

Fable Samples using Pixi:
Pixi graphics API use sample: https://gist.github.com/whitetigle/f1bd57859ce4a3f9fd9155f38047cfca

Instead of porting my app, The best move I can do now is to port the official samples so we check everything is working.
So for now I will put them in a public repository so we can double check the code is clean and is following fable guidemines and then if you wish we'll be able to add them somewhere in the Fable repository in a samples section.

[EDIT]: just found the samples dir https://github.com/fable-compiler/Fable/tree/master/samples/browser/pixi

However since there are a lot of samples, I wonder how to define a single build file to generate the .js files from all the .fsx files located in a folder. I have not yet found a way to do this easily using fableconfig.json (any hint in the doc?) Do I need to use a .fsproj file? (excuse these newbie questions)

[EDIT 2] I will go for a simple system script for now :)

@whitetigle
Copy link
Author

So far so good. Went up the 5 first samples without any problem. (although it may not be the best f# code one can read since it's quite new for me)

Anyway I have prepared a simple html page that lists the samples and loads each of them dynamically showing both the f# source code and the sample. It's not live yet (in a private repository). I will go through all the samples and then we'll see what to make of all this codebase.

Screenshot: https://postimg.org/image/coogcm4c5/

@alfonsogarciacaro
Copy link
Member

Sorry for the late reply. This is an awesome job! 👏 It'd be fantastic to add the samples to Fable's website 😄 I'll try to answer your questions now:

Regarding the Erased unions, I was following the example from https://fable-compiler.github.io/docs/interacting.html page:

Thanks for pointing this out! This was actually a bug in Fable, it should be possible to type test erased union types (e.g. match x with U2<string,int>.Case1 s -> ... will translate to typeof x === "string" ? ...). It will be fixed in the next release 👍 However, in your specific case I think unboxing is fine. Of course unbox is less safe than a type test, but there both branches were casting to SystemRenderer so it should be fine.

If I encounter other problems may I ping you from time to time? If so is is better to use gitter for that or submit an issue?

Of course you can ping me :) Gitter is good for short questions and there're also other people listening there so you may get an answer faster there. But GH issues are also good to record answers so other people can easily find the info. Whatever is more convenient for you will work.

Is there a way to define default values instead or make options becomes REALLY optional (create the object in JS without the field)

Yeah, this is an issue as unfortunately there's no way in F# to have optional members 😕 But as most of the times in JS this is done for option objects (as in this case) we have an alternate way: key value lists. Unfortunately, ts2fable won't create this type of definitions but it's easy to do it yourself:

[<KeyValueList>]
type RendererOptions =
    | View of HTMLCanvasElement
    | Transparent of bool
    | Antialias of bool
    | AutoResize of bool
    | Resolution of float
    | ClearBeforeRendering of bool
    | PreserveDrawingBuffer of bool
    | ForceFXAA of bool
    | RoundPixels of bool
    | BackgroundColor of float

Now you can only specify the options you need:

let currentRenderer =
    let options = [
        Antialias true
        Resolution 1.
        BackgroundColor (float 0x000000)
    ]
    Globals.autoDetectRenderer( 800., 600., options )
    |> unbox<SystemRenderer> 

However since there are a lot of samples, I wonder how to define a single build file to generate the .js files from all the .fsx files located in a folder. I have not yet found a way to do this easily using fableconfig.json (any hint in the doc?) Do I need to use a .fsproj file?

It depends if you consider all samples to be part of the same project (in this case we may think of a project as one HTML page). fableconfig.json doesn't support multiple projects but if you want to compile all samples at once in the same project, you can use .fsproj or just another script that loads the others with the #load directive (example). For the websites samples we usually use a script file with a tutorial and a small configuration to automatically set up the web page. See this issue for an explanation of the process: fable-compiler/Fable#162

I hope this helps! Please tell me if you have more questions and thanks a lot for your contribution!

@whitetigle
Copy link
Author

Hi!
Thanks for your feedback. So here I am with some design problems: I am trying to find a safe way to pass a parameter that uses default values from JS.

context: http://pixijs.github.io/examples/index.html?s=demos&f=movieclip-demo.js&title=MovieClip#/basics/render-texture-v3.js

So in the JS sample there's the following piece of code

var rt = new PIXI.RenderTexture(renderer, 300, 200, PIXI.SCALE_MODES.LINEAR, 0.1);

So as you can see we've got this PIXI.SCALE_MODES.LINEAR value which comes from pixi.js

    SCALE_MODES: {
        DEFAULT:    0,
        LINEAR:     0,
        NEAREST:    1
    }

So I still have this problem regarding default values and how to make sure we don't end up putting some wrong values in critical parts of the applications especially when these values already exist in JS

So in our Pixi bindings our SCALE_MODES gets converted to

    type ScaleModes =
        abstract member DEFAULT: float
        abstract member LINEAR: float
        abstract member NEAREST: float

F# 1st way : way too complicated in my opinion and clearly not safe

type ScaleMode() =
  interface ScaleModes with
    member this.LINEAR = 0.
    member this.DEFAULT = 0.
    member this.NEAREST = 1.

let mode = new ScaleMode()
let linear = mode :> ScaleModes

let rt = new RenderTexture(U2.Case2 renderer, 300., 200., linear.LINEAR, 0.1);

F# 2nd way : same result and really not safe

let LINEAR = 0. // this is the value as found in pixi.js code
let rt = new RenderTexture(U2.Case2 renderer, 300., 200., LINEAR, 0.1);

F# 3rd way : safe as long as we don't emit wrong code. Too slow when it comes to testing: we must compile and then check at runtime

[<Emit("PIXI.SCALE_MODES.LINEAR")>]
let LINEAR : float = failwith "JS only"
let rt = new RenderTexture(U2.Case2 renderer, 300., 200., LINEAR, 0.1);

I did not use Key Value lists since in this case it would not help better

Of course, I may miss other ways to do this since I miss experience in F#.
That's why I would like to know what approach you would choose? Any other safer way?
Thanks!

@MangelMaxime
Copy link
Member

We could used the third solution like this:

module PIXI =

  type SCALE_MODES =
    [<Emit("PIXI.SCALE_MODES.LINEAR")>]
    static member LINEAR : float = failwith "JS only"

Here is how we can access it:

let access = PIXI.SCALE_MODES.LINEAR

Which is transparent with Pixi way from JavaScript point of view.

@whitetigle I am also interested in Pixi.JS binding. If you want we could discuss on Gitter private channel to make our effort coordinate.

@whitetigle
Copy link
Author

whitetigle commented Aug 25, 2016

Hi @MangelMaxime
I am currently evaluating using Fable/F# on my upcoming projects. Since I do a lot of things in Pixi, I wanted to try if I could port the samples easily. A way for me to get better at F# and see if the functional approach would allow for more robust apps as well as lighter codebase.

So far, my F# experiments have proven much more concise and robust that my previous attempt at functional code style using scala. Also the code is much more easier to read.

So since I have some time to dig further these days, my next goal is to port one of my former Pixi project made in Haxe and evaluate the Pros & Cons of this approach.

So for starters, with the help of @alfonsogarciacaro, I have started to port the official samples from Pixi.js; I am nearly done with the 12 samples from the basics section (http://pixijs.github.io/examples/index.html?s=demos&f=movieclip-demo.js&title=MovieClip#/basics/basic.js)

Since the v4 just came out, of course I will be very interested to work with it and if we can make the codebase more robust with solutions like you mentioned then it will be a win for everybody.

I must repeat but I am a F# newbie so these days I often stumble on basic stuff mostly things I haven't yet played with. (like fun vs Func this morning) But I learn fast.

So like I said to @alfonsogarciacaro I think can help here by porting the samples and post issues / thoughts on the bindings to eventually have a clean, readable and robust codebase.

Now regarding the V4, since it's very new, I think that we should keep a v3 and v4. The v3 has been there for quite a long time so it would allow non F#/Fable users - if such audience exists - to speed up their porting and it would also allow for a fallback.

I say this because this morning when working on the TextStyle object I saw that the way to declare a font has changed leading to breaking changes. So we need to be careful with this to avoid at all costs errors coming from wrong bindings. I wrote/used several bindings in Haxe and Scala so I know how much time one can lose to eventually find that the bindings where plain wrong.

Anyway, I will be glad to help as much as I can and also ask for help when needed.
(we can continue on gitter later if you want)

++

@alfonsogarciacaro
Copy link
Member

alfonsogarciacaro commented Aug 25, 2016

In general I think it's not a good idea to access modules directly with EmitAttribute because sometimes Fable may change the name of the module internally to avoid conflicts. In any case, you don't need to implement the interface here, you should be able to do something like PIXI.Globals. SCALE_MODES.LINEAR (see this) Can you check if that works for you?

Please note that, because of the way TypeScript definitions are usually written, "static" values in the bindings won't be represented as static members in F# but just as a normal interface and then you can access an instance of this interface in the Globals wrapper. You can find more info in ts2fable README on how the bindings work and why this Globals wrapper is needed.

@whitetigle
Copy link
Author

PIXI.Globals. SCALE_MODES.LINEAR works just fine. Thanks!
I'll check the ts2fable README.

@MangelMaxime
Copy link
Member

@whitetigle Ok.

And thanks for showing the breaking change. I will play a little with v3 so in order to used a more "mature" lib. And will migrate later when confident with Pixi. :)

@whitetigle
Copy link
Author

I just set my repository to public.
So if you wish you can use/check my samples to check: https://bitbucket.org/fnicaise/fs_press

  • src folder contains the .fsx scripts
  • out folder contains the .js outputs
  • make to build the .js (I needed a simple build tool)

you can serve the files from the root and see the samples + their relative .fsx
It's not exactly clean, but it works for starters.

Note: Bitbucket is my by default source code server to play. So I started there. But I can move to github if needed early.

@MangelMaxime
Copy link
Member

That really cool thanks. :)

I just needed to change the name in the package.json file because it's wasn't installing the dependencies as it's not compliant with the convention I think. "name": "pixi-fsharp-samples",

@whitetigle
Copy link
Author

whitetigle commented Aug 25, 2016

Ah yes, sorry, edited that. Thanks!

I have 2 new questions regarding the current sample:
http://pixijs.github.io/examples/index.html?s=demos&f=movieclip-demo.js&title=MovieClip#/basics/custom-filter-v3.js

1 - createObject syntax

I needed to create an uniform like in the sample and since the function was waiting for an obj I ended up writing this

  let customUniform = createObj [
                        "customUniform" ==>
                            createObj [
                              "type" ==> "1f"
                              "value" ==> "0"]
                        ]

While it works, is this the relevant syntax or is there another way to write objs?

2 - casting an obj to a string
I am loading the fragment shader file and so I get a resources object with the data inside. I end up with:

let fragmentSrc = resources?shader?data

I then cast the data to a string like this:

let fragmentSrcString : string = sprintf "%O" fragmentSrc

Is this the correct way to do this in F#?

[EDIT]
3 - ResizeArray shortcut
Is there a shorter way to create/use a ResizeArray ?

let filters = new ResizeArray<AbstractFilter>()
filters.Add(filter)
bg.filters <- filters
bg.filters = [filter];

4 - change mutable value from an obj
I did not find how to port this piece of code:

filter.uniforms.customUniform.value += 0.04;

in F# filter.uniforms is a property then how can I change the value of its inner object (if that's an object?) I was thinking about reading then writing the value like this

let curValue = filter?uniforms?customUniform?value
filter?uniforms?customUniform?value <- curValue + 0.04

But curValue being an obj I can not add it to a float (I know it is trivial but I can't find it in the docs)

Thanks!

@alfonsogarciacaro
Copy link
Member

Fantastic work, @whitetigle! I had a look at the samples and they look really good 👏 As you commented, the "least elegant" part is option declaration. So I just pushed I new version (0.0.4) of the Pixi bindings that replaces the interfaces with the KeyValueList defined above. Could you give it a try by following the instructions in my previous comment?

1 - createObject syntax

Yes, that's the correct syntax. It may be a bit verbose but I wanted to avoid conflicts. You can still define your own operators to make it terser as in this example (inlining the operators avoids unnecessary function calls in the generated JS code).

2 - casting an obj to a string

That syntax is correct, but if you just want to convert the object to string with no additional formatting you can simply use the string operator:

let fragmentSrcString = string resources?shader?data

If you just want to cast the object to string. That is, you're sure the object is a string and you want to tell that the compiler (of course, this is less safe) unbox will also do:

let fragmentSrcString = unbox<string> resources?shader?data
// let fragmentSrcString: string = unbox resources?shader?data // This is equivalent syntax

3 - ResizeArray shortcut

Yeah, ResizeArray can be a bit verbose. Actually, both F# ResizeArray and fixed arrays compile to the same JS array but the ts2fable parser must pick one so it picks ResizeArray by default. However you can construct ResizeArray directly from a literal array:

bg.filters <- ResizeArray [|filter|]

(It's a bit more performant if you use a literal array but it works with any seq collection.)

@alfonsogarciacaro
Copy link
Member

4 - change mutable value from an obj

F# doesn't include the += to discourage this kind of mutations but they're indeed needed when dealing with some JS libs. Every time you're dealing with dynamic programming and the compiler complains unbox is your friend :)

let curValue = unbox<float> filter?uniforms?customUniform?value
filter?uniforms?customUniform?value <- curValue + 0.04

@whitetigle
Copy link
Author

I just retrieved your latest version but compilation fails:

>Unexpected keyword 'and' in definition (L395,4-L395,7) (Fable.Import.Pixi.fs)
> Incomplete structured construct at or before this point in implementation file. Expected incomplete structured construct at or before this point or other token. (L427,8-L427,14) (Fable.Import.Pixi.fs)

I tried to change this but ionide crashes Atom each time I load such a big file. Will try in VS.

@whitetigle
Copy link
Author

whitetigle commented Aug 25, 2016

Error is right there (line395)

[<KeyValueList>]
    and RendererOptions =
        | View of HTMLCanvasElement
        | Transparent of bool
        | Antialias of bool
        | AutoResize of bool
        | Resolution of float
        | ClearBeforeRendering of bool
        | PreserveDrawingBuffer of bool
        | ForceFXAA of bool
        | RoundPixels of bool
        | BackgroundColor of float

[EDIT]
tried the following syntax and it compiles

and [<KeyValueList>]
        RendererOptions =
        | View of HTMLCanvasElement
        | Transparent of bool
        | Antialias of bool
        | AutoResize of bool
        | Resolution of float
        | ClearBeforeRendering of bool
        | PreserveDrawingBuffer of bool
        | ForceFXAA of bool
        | RoundPixels of bool
        | BackgroundColor of float

Going to check the samples now.

@whitetigle
Copy link
Author

whitetigle commented Aug 25, 2016

OK with patch applied it works! Bravo @alfonsogarciacaro @! 👍
I am going to update all the samples now.

[EDIT] tried unbox and ResizeArray and it works. Thanks!

@alfonsogarciacaro
Copy link
Member

Damn! I should have checked better before publishing, sorry for that! 😅 I fixed the error following your indications and republished the bindings (0.0.5).

Is Atom crashing with a file of that size? I'd recommend Visual Studio Code, it has more or less the same features and Ionide is available for it too 👍

@whitetigle
Copy link
Author

Well I do have VS Code somewhere so I will give it a try. Thanks!

@whitetigle
Copy link
Author

Ok, here I am for some new questions (sorry!).

In javascript I have

button.on('mousedown', onButtonDown)
function onButtonDown()
{
    this.alpha = 1;
}

where this is the current button.

Here is my current attempt in F# which fails

let onButtonDown context  =
  let button = unbox<Sprite> context?button
  button.alpha <- 1.

let context = createObj[ "button" ==> button ]
button?on("mousedown", fun e -> onButtonDown(context)) |> ignore

it fails to capture the local context and thus is applied to the last button I set (I create buttons in a loop). Passing the button object leads to the same result.

So how can I do this in F#?
Thanks!

@alfonsogarciacaro
Copy link
Member

Argh! I hate when JS libs do funny things with this, that was the reason I have to give up the Vue bindings for Fable. Can you point me to the full JS source?

@whitetigle
Copy link
Author

In Haxe we do keep the local context. For instance:

button.click = function( data ) {
  doStuff(button);
}   

But the first years (back in 2008 I think) we had to create a local context object and pass this object.

I tell you this because the Haxe compiler is made in OCAML so maybe there are things to get from here? (Not a compiler guy myself. It's just a thought)

@alfonsogarciacaro
Copy link
Member

If you can attach the event handlers directly to the object, deriving may work. Could you please try this?

type MyButton(texture) =
    inherit PIXI.Sprite(texture)
    member this.mousedown() =
        this?isdown <- true
        this?alpha <- 1.  

@whitetigle
Copy link
Author

Well... Bravo! 👍
ohyeah

@whitetigle
Copy link
Author

whitetigle commented Aug 25, 2016

Hi! Last question for today!

I have started to port this sample: http://pixijs.github.io/examples/index.html?s=demos&f=movieclip-demo.js&title=MovieClip#/demos/text-demo.js

Here we mix Text and BitmapTextObjects. So I started fiddling with our Pixi Bindings to use KeyValueList instead of long interfaces which led to:

  and [<KeyValueList>] 
        TextStyle =
        | Font of string
        | Fill of U2<string,float>
        | Align of string
        | Stroke of U2<string,float>
        | StrokeThickness of float
        | WordWrap of bool
        | WordWrapWidth of float
        | LineHeight of float 
        | DropShadow of bool 
        | DropShadowColor of U2<string, float> 
        | DropShadowAngle of float 
        | DropShadowDistance of float 
        | Padding of float 
        | TextBaseline of string 
        | LineJoin of string 
        | MiterLimit of float 

I did the same for BitmapTextStyle and ended up with :

        [<KeyValueList>]
        type BitmapTextStyle =
            | Font of U2<string, obj>
            | Align of string 
            | Tint of float 

I then modified parameter accesses whenever it was needed from TextStyle to TextStyle list and the same for BitmapTextstyle.

However F# does not seem to like having KeyFontValue types with Keys having the same name thus leading to errors when I try to do this:

// BitmapTextStyle
  let fontStyle = [
    Font (U2.Case1 "35px Desyrel")
    Align "right"
  ]
  let mutable bitmapFontText = new extras.BitmapText("pixi bitmap fonts in f# are\n now supported", fontStyle)

// TextStyle
let style = [
  Font "35px Arial"
  Fill (U2.case1 "white")
  Align "left"
]
let mutable textSample : Fable.Import.PIXI.Text = Fable.Import.PIXI.Text("Rich text with a lot of options and across multiple lines", style)

In fact my problem is that the compiler does not know how to make the difference between both styles. So what can I do?
Thanks!

(updated bindings here: https://gist.github.com/whitetigle/b03e4689823d2950b711a81d4793df04)
[EDIT] sample source code there: https://bitbucket.org/fnicaise/fs_press/raw/1cff44eb7ab90c2d90f20ac53810096c3b2d7e3f/src/demos/text-demo.fsx

@whitetigle
Copy link
Author

All pure pixi samples done!

Notes:

  • One sample in the picture section does not work (as written in its comments) the other one needs a plugin
  • DisplayList: samples need appropriate plugins
  • Spine: samples need appropriate plugins
  • DirectDrawSurfaces: samples need appropriate plugins

@whitetigle
Copy link
Author

Pixi being in the process to work, I will need

  • Howler.js for sound
  • a tweening library

So, eventually I will have to port Howler.js too :)
Regarding the tweening lib, do we have one we can already use or should I go for a JS one?

@alfonsogarciacaro
Copy link
Member

By tweening you mean purely math operations? It that's so, maybe there's something for in F#/.NET we could translate, but I'm not sure. If it requires something more platform related (like image manipulation), it'll be difficult to translate and it's probably better to find a JS lib.

@whitetigle
Copy link
Author

OK. I'll see. Thanks!

@alfonsogarciacaro
Copy link
Member

@whitetigle I reviewed the samples, great job! :) I've made some small changes, mainly about formatting (shortening lines so they fit better in the web, etc), in the hope they will make the code a bit clearer for other users. You can check them and ask me if you have any question :) 621e856

@whitetigle
Copy link
Author

whitetigle commented Sep 5, 2016

@alfonsogarciacaro thanks! Cleaner is better! 👍
And I can now remove "check & clean mutable" from my todo list 😉

Question : what purpose lies behind the redesign of the animate function?

[EDIT]

You wrote a few days ago;

This was actually a bug in Fable, it should be possible to type test erased union types (e.g. match x with U2<string,int>.Case1 s -> ... will translate to typeof x === "string" ? ...). It will be fixed in the next release

so does this sample mean we're good to go?

let isWebGL, renderer =
  match Globals.autoDetectRenderer(800., 600.) with
  | U2.Case1 webgl -> true, webgl :> SystemRenderer
  | U2.Case2 canvas -> false, canvas :> SystemRenderer

Also what does the false,xxx syntax mean?

U2.Case2 canvas -> false, canvas :> SystemRenderer

Thanks!

@alfonsogarciacaro
Copy link
Member

@whitetigle The refactoring of animate function is a common pattern to keep the state (in this case, the count variable) of the function private. count now is trapped in a closure by the inner animate but it's not accessible from the outside so you can be sure nobody else is modifying the value. I hope that makes sense 😄

And yes, the bug for making pattern matching with erased unions is fixed 👍 Thanks a lot for discovering it! In the sample above, returning a tuple and then destructuring in the assignments is a common pattern in F# to return two values in one go (webGL is the boolean value, and renderer the result of autoDetectRenderer upcasted to SystemRenderer).

@whitetigle
Copy link
Author

@alfonsogarciacaro Thanks for these explanations! Everything's quite clear!

Now I have been playing a little bit to port one of my project.
So I started with a simple structure with the following rules:

  1. the state of the game can change. For instance after the init animation (Init state), I want to enter the Play state. Then if I die, the GameOver state. each state is responsible for it's own logic and rendering.
  2. while I can derive a state from the previous one I need a way to make it evolve as needed. For instance after GameOver, I want to be able to choose a state relative to what happened in the game.

Here a sample which is directly applied from one of my very small project.

type GameStates =
    | Init
    | LevelUp
    | Play
    | GameOver of GameStates

let rec animate =
   let mutable state = Init
   let rec animate (dt:float) =
     count <- count + 1.

     match state with
        | Init ->
          printfn "Init"
          if count > 100. then
            state <- Play

        | Play ->
          printfn "Play"
          match Math.random() * 10. with
            | x when x < 2. ->
              state <- GameOver Init
            | _ ->
              match count with
                | x when x > 100. ->
                  state <- LevelUp
                | _ -> ()

        | LevelUp ->
          printfn "LevelUp"
          count <- 0.
          state <- Play

        | GameOver nextState ->
          printfn "gameover"
          count <- 0.
          state <- nextState

     renderer.render(stage)
     window.requestAnimationFrame(FrameRequestCallback animate) |> ignore
   animate

  // start animating
animate 0.

My main problem is since window.requestAnimationFrame needs a float->unit how can I actually pass a State and then pass an updated copy so we remain into the immutable realm.
Is there any functional design pattern that would match this case? Is this possible?

Thanks a lot for your help!

@MangelMaxime
Copy link
Member

MangelMaxime commented Sep 5, 2016

@whitetigle I am not sure but I think the architecture used in the project https://github.com/fable-compiler/fable-virtualdom can be a solution.
I have personally tried it at the beginning when working GUI lib over Pixi and it's was promising.

The idea behind Fable Architecture is that we react over Message that with use to update the Model and when the Model has been updated with can Draw the view from the Model.

When playing with the architecture I needed something like 50-60lines to support manage the application. I don't know if it's what we should go with for games. Perhaps wait for the answer of Alfonso

@whitetigle
Copy link
Author

@MangelMaxime thanks for the hint! I will definitely take a look.

@alfonsogarciacaro
Copy link
Member

@whitetigle It'd be nice to adapt the architecture developed by @mastoj and @MangelMaxime to a game engine (aka Fable architecture 😉). I've to admit I didn't have much time to play with it myself so I'm not sure if it's a good fit or not.

Probably @MangelMaxime can correct me, but my concern is the Fable architecture (as well as other architectures designed for business applications like Elm or Redux) is event-driven (the updates are triggered when an event like a button click happens in the GUI) but in a game loop updates happen for every frame.

At a very basic level what you need is an asynchronous recursive function so you can feed the function with the updated state instead of storing a copy somewhere and keep everything immutable and nice 😸 This can be done with F# async computation expression. For example:

// Convert requestAnimationFrame to Async
let awaitAnimationFrame() =
  Async.FromContinuations <| fun (cont,_,_) ->
    window.requestAnimationFrame(FrameRequestCallback cont) |> ignore

let rec animate state = async {
  let state = update state // Update the state
  let! _ = awaitAnimationFrame() // Wait for next frame
  return! animate state // Recursive call
}

animate initState |> Async.StartImmediate

Maybe this meets your needs. I've modified the blur-filter sample in the async-recursion branch to check the code works... and it does 👍

@MangelMaxime
Copy link
Member

You are true about that Fable architecture is event-driven. But it's can also react to subscriber in order to push an update. Here is an example of a clock.

I think this should be sufficient for certain type of games for example, this tetris game is made using Elm and the Elm architecture. (Fable architecture is actually a port from the elm one).

Another solution is to follow the implementation shown by Elm games samples for example this mario game ported to Fable: https://fable-compiler.github.io/samples/mario/index.html

But here the abstraction is really thin.

There are others solutions existing in Elm, Purescript etc. but I find the two quoted above the most versatile and powerful. Because both enforce you to structure the program over a model.

Initial Model -> Update Model -> Render Model -> Update Model -> Render Model -> ...

And I find this structure really nice and you can build a lot of great tools over it to help you. Like time travel, watcher, debug (what again a time travel :) )

@alfonsogarciacaro
Copy link
Member

Right! I missed the part about producers in Fable architecture, sorry 😅

@MangelMaxime
Copy link
Member

No problem :) I work with it all the day and night that help a little :)

@whitetigle
Copy link
Author

First of all, thank you both for your help. I begin to get a clearer and broader vision of F# and Fable. And I like it a lot 😄

So for starters I tried a first approach with the async idea from @alfonsogarciacaro.

type GameState =
  {
    Count: float;
    Level: int;
  }

type State =
    | Init of GameState
    | LevelUp of GameState
    | Play of GameState
    | GameOver of State * GameState

let awaitAnimationFrame() =
  Async.FromContinuations <| fun (cont,_,_) ->
    window.requestAnimationFrame(FrameRequestCallback cont) |> ignore

let rec animate state = async {
  let state =
    match state with
    | Init state->
      printfn "Init"
      let updatedState = {state with Count = 0.; Level = 0}
      Play updatedState

    | LevelUp state ->
      printfn "LevelUp"
      let updatedState = {state with Level = state.Level + 1}
      printfn "Level is now %i" updatedState.Level
      Play updatedState

    | Play state ->
      printfn "Play"
      let updatedState = {state with Count = state.Count + 0.1}
      match updatedState.Count with
      | x when x > 100. ->
        let stateAfterGameOver = Init updatedState
        GameOver (stateAfterGameOver, updatedState)
      | value ->
        let nextLevel = (Math.floor(value) |> int) % 2 = 0
        if nextLevel then
          LevelUp updatedState
        else
          Play updatedState

    | GameOver (nextState, gameState) ->
      printfn "GameOver"
      printfn "Level is %i" gameState.Level
      nextState

  renderer.render(stage)
  let! _ = awaitAnimationFrame()
  return! animate state
}

// start animating
let startingState = {Count=0.; Level=0}
let StartState = Init startingState
animate StartState |> Async.StartImmediate

So it's a basic loop that updates a GameState record.
The game begins (Init) then enters it's core logic Play. Every few frames, we increase the level (LevelUp). After a while the game stops (GameOver) and comes back to Init.

There's the render call for the current frame that does nothing in this sample but would update sprites, effects, etc..
There aren't any event. And I haven't taken time yet to check how they would fit.

So it is working, it looks like what I was looking for and I think it's a good basis for discussion.
So now @MangelMaxime do you think this could somehow be adapted to Fable architecture?

@MangelMaxime
Copy link
Member

@whitetigle Yes this could be adapted to Fable architecture. I will try to implement a simple version with your sample just to have a comparison.
I keep you inform.

@alfonsogarciacaro
Copy link
Member

I must also say that I overengineered my solution above a bit. The same effect can also be achieved without async in this case 😅

let rec animate state =
  let state = update state
  renderer.render(stage)
  window.requestAnimationFrame(FrameRequestCallback(fun _ -> animate count)) |> ignore

animate initState

@whitetigle
Copy link
Author

FrameRequestCallback(fun _ -> animate count) Well done! I updated my code.

@whitetigle
Copy link
Author

@MangelMaxime I was wondering this morning about having the ui components in the dom to leverage the virtual dom approach and let the core game rendering with requestAnimationFrame as usual.
Now the questions is how would they communicate. Through a shared Model?

@MangelMaxime
Copy link
Member

@whitetigle VirtualDom support what we call Producers and Subscribers.

So if you want to communicate with the external world you can use them. For example, this is how we are managing the SPA approach for the router part (the browser detect the hashchange and push the information in the app).

  • A producer is something that push message into your application.
  • A subscriber is a function which will be notify by the application on every message handle by the application.
    And as everything that happen in the application is handle by a message you can listen what ever your want.

Here is a sample of a Producer: https://github.com/fable-compiler/fable-virtualdom/blob/master/samples/clock/clock.fsx#L68

Here is a sample of a Subscriber: https://github.com/fable-compiler/fable-virtualdom/blob/master/samples/subscriberexample/subscriberexample.fsx#L54

Also, in a future version of VirtualDom we will have support for a plugin structure which will allow us to capture not only the messages but also the Model.
Here is the current sample of the plugin usage. ⚠️ Still under development

At the current point I think Producer/Subscriber can be enough because I don't think we want to share the whole model between the Pixi world and VirtualDom world. The Model will have more information than just the useful one like HP amount, clicked, etc. For example, in VirtualDom you need to keep track of the value in the input.

And as I am working on the Fable Architecture version for Pixi binding we could probably have a Pixi App and a VirtualDom App. I hope to publish a first version today.

Off scope of your question:
Finally, I understand that having the GUI through DOM context could be a big improvement but I don't like CSS and HTML. So I am working on a Library which will allow to have a full web app working inside a Canvas. So later we could also offer this solution to users too.

@MangelMaxime
Copy link
Member

@whitetigle I have pushed on a new branch a sample showing how we could implement the Fable Architecture over Pixi.

I like this kind architecture which enforce the separation between the View / Model / Update. But here the view function is quite hacky because we don't have the support of VirtualDom / React to help us handle the diff, patch, rendering etc.
However this can give us a good start to see if this kind of architecture is suitable for us or not.

I think you have a better experience of Pixi than me and probably you will another way of handling the view. I don't really know how we use Pixi to do that :) (learning it since 1 week^^)

I put a lot of comment to help understand what's going on but if you have a question don't hesitate to ping me.

@alfonsogarciacaro Ping you, if you want to take a look.

From my point of view, this architecture could be viable if we find a better way to handle the View part. If you have any idea I am open :)
Otherwise, we could try to play with your last proposition to enforce the architecture.

Perhaps we should try to make a really simple game like pong with both and try to see which one fit the best our needs.

@whitetigle
Copy link
Author

Well just a few thoughts about pixi before I dwelve into the details of your sample (very impressive by the way 👍 ) :

We do need requestAnimationFrame since that's the only method that guarantee that the next draw will happen as soon as possible. It's kind of Browser enforced.

SetInterval tries as fast as it can but can be delayed by system calls or whatever "noise" so there's absolutely no guarantee we'll get a stable frame rate.

Then we would have different needs:

  • slow rendering objects: for instance UI that do not need to update as fast as possible. In most cases, UI is controlled through tweening which is independent from the render loop speed since it's time based. The render just needs to refresh the screen with the updated values. That would be a good candidate for our tests.
  • fast rendering objects: particles, and all the objects that need fast updates on the screen. Pixi takes care of them and is already optimized so there's no need to update them outside of the render loop.

Then we do have events that need to interact with both kind of objects.
For UI no problem, we usually bind events to UI components and voilà. But sometimes UI controls fast rendering objects: increase speed, etc... So we need to be able to communicate.

For fast rendering we have two scenarios:

  1. bind events to the objets. For instance click on a foe to kill him.
  2. bind events to the stage to detect and interpret player behavior

There too Pixi takes care of the events for us and we want to communicate as fast as possible to avoid any lag.

Now all games are different.

  • A match3 would need slow rendering apart for particle effects.
  • Pong would mostly need fast rendering for display and events
  • mario would need a very stable framerate
  • a Schmup would need a very stable and fast framerate too
  • a click & play would allow for very interesting tries since it's usually playing anims at a given framerate but not faster (after all we don't want Guybrush Threepwood to run through a pirate! )

So most of the games want 40-60 fps and they want it smooth (hence the need for requestAnimationFrame). But there's an outstanding number of games that do not need more.
For the others (FPS for instance) I think we're out of scope. Unreal Engine is their friend.

So there are several choices we could make that would just be pragmatic and allow us to ask for fast rendering just when it's needed, not for all components, not all the time if it allows us to have a good and robust architecture.

So I will dive into your code and try to understand as much as I can.
Thanks for your work 👍

@MangelMaxime
Copy link
Member

MangelMaxime commented Sep 6, 2016

Thanks for all this explanations and this is what I what thinking when writing the sample.

For me we should dig in the direction of your proposition and the mario architecture. And perhaps simply offer the user helper library like Keyboard event management, tween, etc.

But not "forcing" them to Fable Architecture or a similar one has it's probably to complex for a game. We could probably simply say here is the recommended way to go with Fable + Pixi and let them experiment they own architecture if they want.

I have always hated play with Engine because they force you to do things their way and usually I don't like it :).

[Edit]: I will wait your impression on the work done. And perhaps we could simply take inspiration from part of the work.

@alfonsogarciacaro
Copy link
Member

@MangelMaxime Great work! I've limited access to my computer until Friday so I cannot have a deep look yet but I'll check later to see if I can so some comment (though I think you've now much more expertise on Fable architecture than me 😉). In any case, I agree with @whitetigle that requestAnimationFrame should be used. I think Elm uses it now for DOM rendering too, and it has several niceties: the browser automatically adjust the frame rate to the device power, pauses the animation if the tab isn't visible, etc.

@MangelMaxime
Copy link
Member

Yes I agree too about the requestAnimationFrame just did think about it on the moment. I will change the producer to use this function.

Probably true about Fable architecture but you still have a better understanding of the Functional paradigm :) So you could probably think of a better way to handle the view function which is not has good has it should be ^^. (But I didn't find a good structure).

@whitetigle
Copy link
Author

Ok. I would like to make a test with porting bunnymark http://www.goodboydigital.com/pixijs/bunnymark_v3/ to your architectrue and see what happens.

What do you think? Could you just update the code so we use requestAnimationFrame? I am not sure how to make that change in your code. Thanks!

@whitetigle
Copy link
Author

Hi!
I just pushed a little pixi demo there: pixi |> fable

If it's ok for you, Since it's quite a complete sample, I plan to add it to the samples when done with the inline documentation.

Featuring:

  • Graphics API
  • Sprites
  • textures
  • masks
  • events
  • filters
  • bitmap texts
  • Containers
  • and some easy to use maths

So a kind of Pixi soup 😉
I need to rework a bit the layout to better fit 16:9 displays. But it works fullscreen and is even nicer in windowed mode.

To change the speed of the animation, just move your mouse close or far from the center of the screen. farther = faster. closer = slower. Mouse up and down to fade the animation et voilà!

Last but not least, I will push some new changes to the bindings this week since I have found new little optimizations.

@alfonsogarciacaro
Copy link
Member

@MangelMaxime
Copy link
Member

@whitetigle It's a really cool sample. I love it :) Nice work 👏

@whitetigle
Copy link
Author

Thanks! 😄 I'm glad you enjoy it!

Oh, by the way, Uuntil I'm ready to push the sample, the source is there

One more thing, @alfonsogarciacaro since you use twitter, and I can't get access anymore to my old account @whitetigle, I have created an new account: @thewhitetigle.
So if you wish to link to my account, please use the new nickname. Thanks!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants