I recently rewrote an old Java game of mine ( Gundam v2 ) in Fantom, and updated it to run over JavaScript. This seemed to generate some interest on Sidewalk as to how I found programming the game in Java vs programming it in Fantom. And also any complications I found in getting the Fantom app to run in a JavaScript browser. I'll try to answer a couple of those queries below:
.
Java -> Fantom
I had been eagerly looking for an excuse to rewrite Gundam for some time now, and exploring features of Fantom gave me just that. Following are some noteworthy design decisions and opinions formed during development:
Fantom Goodness
In general the Fantom code is more terse, smaller and easier to follow. But you already knew that! Little things like the field getters and setters, functional lists and built in human readable / editable serialisation meant I was able to delete swaths of code!
Function Callbacks
In Java land, to clip sprites on or off the screen and other discrete sets of functionality, enums and switch statements was generally the pattern used. In Fantom land I changed this to use function callbacks. The behaviour could then be extensibly externalised and passed in.
Static Data
A bone of contention here. For simplicities sake, I chose to use static data to hold caches of Sprites, Images, Sounds, etc... These classes seemed too numerous to make use of sys::Service.find(). Because I didn't want to limit the entire game to run within the UI display thread I didn't want to use Actor.locals either. Hence the static data approach. A simple native static map with both Java and JavaScript implementations did the trick.
The push/pop & translate methods on gfx::Graphics are an awesome idea, only they seem to come with quite a performance hit. So much so, I abandoned the translate(Int x, Int y) method and instead wrapped the given Graphics class, manually performing the needed coordinate translations.
A small library I wrote for the game ( FantomMappy ) needed to use but one method from either Graphics or my wrapped class. Being too lazy to write a Mixin for the one method, I tried dynamic / duck typing which led to Dynamic Typing -> sys::UnknownTypeErr. Essentially dynamic typing doesn't work on the Graphcis object, the current workaround being I have to wrap it all the time.
My game library FantomMappy needed to create tile images from its own image data. But currently you cannot create an image with transparent pixels. The workaround was to pre-compute the image and save it out as a .png, because you can load transparent images.
An issue I still have with the Java version. If multiple keys are pressed at once (e.g. right cursor and fire) then any corresponding KeyUp events are not fired. (Leaving my sprite careering in the wrong direction.) After some digging this seems to be an on-going issue with SWT, not Fantom.
.
Fantom -> JavaScript
In general, getting Gundam v2 to run in a browser was a lot easier than I imagined. Most of the code just worked! That said, following are a couple of hiccups I encountered.
Documentation
By far the biggest obstacle to making an app run in JavsScript is a lack of documentation. There are often glaring omissions to the JavaScript implementation (and for good reasons too) but as none of it is documented, it leaves you to find out the hard way - i.e. attempt to run the app in the browser and see what errors arise.
JS File resources
So your pod references image and property files - how can / does JavaScript reference and access these? This seems to be undocumented, but in general you can create and use images but no other files. (See next point.) To create an Image ensure you use the Image.make(`fan://${podName}/res/${imageName}`) ctor. This then makes a request to http://domain.com/pod/${podName}/res/${imageName} which you need to make sure your server, um, services.
JavaScript has no notion of files hence you can not load, parse or access any. Not even from a URL. (Well, not easily.) Especially not binary files. Needing to load map data from a said binary file, I converted it to a hex Str ( myFile.in.readAllBuf.toHex ) and pasted it into my code as constant. I could then read it back as an InStream with Buf.fromHex(hex).in. (I also compared a MD5 hash of the hex string to ensure it hadn't been corrupted.)
Pod Properties
Are not available in JavaScript. The biggest pain for me was not having the pod version handy for displaying in title screens, etc. No workaround available. I just have to re-hard code these properties in the app.
Animation Loop
A topic I've seen before on Sidewalk. How do you create an animation loop in JavaScript? (A repeating event that fires every 30 ms (ish) so you can smoothly update screen animations to the next frame). In my case, this was also the main game loop.
The concurrent::Actor.sendLater() works well in Java land, but obviously not implemented in JavaScript. (As JavaScript is single threaded, it has a very limited concurrent pod implementation.) So I created a Pulsar class that compensates for long running event handlers and ensures a consistent frame rate. A simple JavaScript implementation made use of the JS's native window.setTimeout.
(I could have used fwt::Desktop.callLater as the JavaScript implementation delegates to window.setTimeout, but again, I didn't want to limit the Java version to run in the UI display thread.)
Want a static const Func for defining default callback behaviour? Can not. This sys::NotImmutableErr bug means no const classes with Func fields. You have to create a new class instance every time you want to reference a field.
I was very impressed that InStream supported endianness and came complete with methods to read signed 2, 4 and 8 byte integers using a configured endian. In Java, this was something I had to code myself - and I was happy to delete all that fiddily code! Just a shame I had to revert the deletions because it wasn't fully implemented in JavaScript. Sigh. Should be fixed in the next Fantom release though.
When an window is opened in JS, the onFocus, onActive and onOpen events are not fired, meaning I have no way to kick off code when the window is initially opened. My workaround was to hook into the onMouseDown event and ask the user to click the window.
I was getting strange UnkownPodErr being thrown. It took me a while to realise the Fantom code was fine, it just doesn't play nicely with other JavaScript frameworks. Notably in my case, prototype.js.
This was more a problem with my F4 setup generating Fantom 1.0.60 code rather than the latest 1.0.63 but it tripped me up none the less. I shall not say more for it was fixed in Fantom 1.0.62 and F4 now ships with 1.0.63.
.
Conclusion
Largely, given the constraints of JavaScript, Fantom code just works in a browser. There are a couple of issues, but they all have workarounds. I would say my main concern is that only 2 of the above issues have been promoted to tickets.
brianSun 19 Aug 2012
This is a great write-up. Thanks for taking the time to do it.
The push/pop & translate methods on gfx::Graphics are an awesome idea, only they seem to come with quite a performance hit.
That is interesting. Was this in SWT or in JS? In Java we don't even create a new underlying context, just push/pop internal state. I wonder if there is a specific call in there that we could optimize?
By far the biggest obstacle to making an app run in JavsScript is a lack of documentation. There are often glaring omissions to the JavaScript implementation
I think this hits the nail on the head, and when I do JS code its the biggest stumbling block. At first JS we sort of just treated JS as a prototype, but now it is becoming one of the most important features. One big fix Andy made recently was to check at compile time if you are using a class in @Js code that isn't marked as @Js. I think we need to enhance this to include methods too. There are too many methods that won't ever be implemented in @Js in the next few years, or maybe ever. I think that would go a long way to removing many of the sharp edges in JavaScript use.
And it might be nice to have some of place where we keep track of all the differences. The goal is not 100% fidelity with Java, so some differences will always be there and we should figure out how to make it easy to find.
SlimerDudeMon 20 Aug 2012
The push/pop & translate methods are quite a performance hit.
That is interesting. Was this in SWT or JS
It was in SWT, though I deleted the push/pop and the translate usage at the same time, so it could just be translate method.
if you are using a class in @Js code that isn't marked as @Js
Yeah, that was really useful.
we need to enhance this to include methods too.
I guess that would apply to native methods. Thought: if you tipped the idea on it's head and had a @NoJS facet then that would be more self documenting (as I imagine most classes & method would have a JS impl). A simple switch in the build.fan could then turn JS compilation off for those pods which don't require it.
be nice to have some of place where we keep track of all the differences.
Yep, I'd be down with that!
yliuTue 21 Aug 2012
In terms of Game Development, how would you say Fantom held up? (Keeping in mind the goal for Cross-Platform capabilities.)
Edit- Do you think it could handle a larger game project? Or maybe even 3D games.
SlimerDudeThu 23 Aug 2012
In terms of Game Development, how would you say Fantom held up?
Just fine - I was surprised at how fast the JS version was, given all the gfx rendering.
Do you think it could handle a larger game project?
Of course. To me, larger games generally boil down to 2 distinct parts:
The Main Game Screen - where the action takes place and where you need the performance. Any bottle necks you encounter here are more likely to do with the underlying VM or API you you're using. e.g. SWT and the JVM. The only performance issues with Fantom I'm aware of, are the List & Map implementations. But if you find you're overusing the default implementations, you could either re-evaluate your usage or code your own simpler version (native or otherwise).
Peripheral Behaviour - title screens, option screens, networking and server-side support. These typically don't don't need big performance (unless you're talking large multi-player) and just need to banged out. For these parts, Fantom is ideal.
maybe even 3D games.
go4 would be in a better position to answer that, given his fan3d - Fantom Graphics System (Admission - I've not looked at it yet...) But as long as you can tap into DirectX / OpenGL then why not?
One thing I keep meaning to do is try getting Gundam to run on the .NET platform. For if you want to go 3D on Windoze machines, I'd imagine native .NET bindings would be the way forward.
SlimerDude Sun 19 Aug 2012
Java -> Fantom -> JavaScript
I recently rewrote an old Java game of mine ( Gundam v2 ) in Fantom, and updated it to run over JavaScript. This seemed to generate some interest on Sidewalk as to how I found programming the game in Java vs programming it in Fantom. And also any complications I found in getting the Fantom app to run in a JavaScript browser. I'll try to answer a couple of those queries below:
.
Java -> Fantom
I had been eagerly looking for an excuse to rewrite Gundam for some time now, and exploring features of Fantom gave me just that. Following are some noteworthy design decisions and opinions formed during development:
Fantom Goodness
In general the Fantom code is more terse, smaller and easier to follow. But you already knew that! Little things like the field getters and setters, functional lists and built in human readable / editable serialisation meant I was able to delete swaths of code!
Function Callbacks
In Java land, to clip sprites on or off the screen and other discrete sets of functionality,
enums
andswitch
statements was generally the pattern used. In Fantom land I changed this to use function callbacks. The behaviour could then be extensibly externalised and passed in.Static Data
A bone of contention here. For simplicities sake, I chose to use static data to hold caches of Sprites, Images, Sounds, etc... These classes seemed too numerous to make use of
sys::Service.find()
. Because I didn't want to limit the entire game to run within the UI display thread I didn't want to useActor.locals
either. Hence the static data approach. A simple native static map with both Java and JavaScript implementations did the trick.gfx::Graphics
The push/pop & translate methods on
gfx::Graphics
are an awesome idea, only they seem to come with quite a performance hit. So much so, I abandoned thetranslate(Int x, Int y)
method and instead wrapped the givenGraphics
class, manually performing the needed coordinate translations.A small library I wrote for the game ( FantomMappy ) needed to use but one method from either
Graphics
or my wrapped class. Being too lazy to write a Mixin for the one method, I tried dynamic / duck typing which led to Dynamic Typing -> sys::UnknownTypeErr. Essentially dynamic typing doesn't work on theGraphcis
object, the current workaround being I have to wrap it all the time.Creating Transparent Images
My game library FantomMappy needed to create tile images from its own image data. But currently you cannot create an image with transparent pixels. The workaround was to pre-compute the image and save it out as a
.png
, because you can load transparent images.SWT Key Repeats
An issue I still have with the Java version. If multiple keys are pressed at once (e.g. right cursor and fire) then any corresponding KeyUp events are not fired. (Leaving my sprite careering in the wrong direction.) After some digging this seems to be an on-going issue with SWT, not Fantom.
.
Fantom -> JavaScript
In general, getting Gundam v2 to run in a browser was a lot easier than I imagined. Most of the code just worked! That said, following are a couple of hiccups I encountered.
Documentation
By far the biggest obstacle to making an app run in JavsScript is a lack of documentation. There are often glaring omissions to the JavaScript implementation (and for good reasons too) but as none of it is documented, it leaves you to find out the hard way - i.e. attempt to run the app in the browser and see what errors arise.
JS File resources
So your pod references image and property files - how can / does JavaScript reference and access these? This seems to be undocumented, but in general you can create and use images but no other files. (See next point.) To create an
Image
ensure you use theImage.make(`fan://${podName}/res/${imageName}`)
ctor. This then makes a request tohttp://domain.com/pod/${podName}/res/${imageName}
which you need to make sure your server, um, services.Binary Files
JavaScript has no notion of files hence you can not load, parse or access any. Not even from a URL. (Well, not easily.) Especially not binary files. Needing to load map data from a said binary file, I converted it to a hex Str (
myFile.in.readAllBuf.toHex
) and pasted it into my code as constant. I could then read it back as anInStream
withBuf.fromHex(hex).in
. (I also compared a MD5 hash of the hex string to ensure it hadn't been corrupted.)Pod Properties
Are not available in JavaScript. The biggest pain for me was not having the pod version handy for displaying in title screens, etc. No workaround available. I just have to re-hard code these properties in the app.
Animation Loop
A topic I've seen before on Sidewalk. How do you create an animation loop in JavaScript? (A repeating event that fires every 30 ms (ish) so you can smoothly update screen animations to the next frame). In my case, this was also the main game loop.
The
concurrent::Actor.sendLater()
works well in Java land, but obviously not implemented in JavaScript. (As JavaScript is single threaded, it has a very limited concurrent pod implementation.) So I created aPulsar
class that compensates for long running event handlers and ensures a consistent frame rate. A simple JavaScript implementation made use of the JS's nativewindow.setTimeout
.(I could have used
fwt::Desktop.callLater
as the JavaScript implementation delegates towindow.setTimeout
, but again, I didn't want to limit the Java version to run in the UI display thread.)Super == Bad
The use of the
super
keyword is usually not required, but it often makes code more readable and lets you know where methods reside. But due to js: Compiler not handling super.constField in ctor correctly #1947 I had methodically delete a lot of mysuper
usages.Func Fields
Want a
static const Func
for defining default callback behaviour? Can not. This sys::NotImmutableErr bug means no const classes withFunc
fields. You have to create a new class instance every time you want to reference a field.InStream Endian
I was very impressed that InStream supported endianness and came complete with methods to read signed 2, 4 and 8 byte integers using a configured endian. In Java, this was something I had to code myself - and I was happy to delete all that fiddily code! Just a shame I had to revert the deletions because it wasn't fully implemented in JavaScript. Sigh. Should be fixed in the next Fantom release though.
Key Down Event
Some Event fields are not populated in JavaScript when the keyDown event is fired. The workaround is not to use the field!
JS Window Events
When an window is opened in JS, the
onFocus
,onActive
andonOpen
events are not fired, meaning I have no way to kick off code when the window is initially opened. My workaround was to hook into theonMouseDown
event and ask the user to click the window.Does not play well with others
I was getting strange
UnkownPodErr
being thrown. It took me a while to realise the Fantom code was fine, it just doesn't play nicely with other JavaScript frameworks. Notably in my case,prototype.js
.Accessor Methods in JS
This was more a problem with my F4 setup generating Fantom 1.0.60 code rather than the latest 1.0.63 but it tripped me up none the less. I shall not say more for it was fixed in Fantom 1.0.62 and F4 now ships with 1.0.63.
.
Conclusion
Largely, given the constraints of JavaScript, Fantom code just works in a browser. There are a couple of issues, but they all have workarounds. I would say my main concern is that only 2 of the above issues have been promoted to tickets.
brian Sun 19 Aug 2012
This is a great write-up. Thanks for taking the time to do it.
That is interesting. Was this in SWT or in JS? In Java we don't even create a new underlying context, just push/pop internal state. I wonder if there is a specific call in there that we could optimize?
I think this hits the nail on the head, and when I do JS code its the biggest stumbling block. At first JS we sort of just treated JS as a prototype, but now it is becoming one of the most important features. One big fix Andy made recently was to check at compile time if you are using a class in @Js code that isn't marked as @Js. I think we need to enhance this to include methods too. There are too many methods that won't ever be implemented in @Js in the next few years, or maybe ever. I think that would go a long way to removing many of the sharp edges in JavaScript use.
And it might be nice to have some of place where we keep track of all the differences. The goal is not 100% fidelity with Java, so some differences will always be there and we should figure out how to make it easy to find.
SlimerDude Mon 20 Aug 2012
It was in SWT, though I deleted the push/pop and the translate usage at the same time, so it could just be
translate
method.Yeah, that was really useful.
I guess that would apply to native methods. Thought: if you tipped the idea on it's head and had a
@NoJS
facet then that would be more self documenting (as I imagine most classes & method would have a JS impl). A simple switch in the build.fan could then turn JS compilation off for those pods which don't require it.Yep, I'd be down with that!
yliu Tue 21 Aug 2012
In terms of Game Development, how would you say Fantom held up? (Keeping in mind the goal for Cross-Platform capabilities.)
Edit- Do you think it could handle a larger game project? Or maybe even 3D games.
SlimerDude Thu 23 Aug 2012
Just fine - I was surprised at how fast the JS version was, given all the gfx rendering.
Of course. To me, larger games generally boil down to 2 distinct parts:
The Main Game Screen - where the action takes place and where you need the performance. Any bottle necks you encounter here are more likely to do with the underlying VM or API you you're using. e.g. SWT and the JVM. The only performance issues with Fantom I'm aware of, are the List & Map implementations. But if you find you're overusing the default implementations, you could either re-evaluate your usage or code your own simpler version (native or otherwise).
Peripheral Behaviour - title screens, option screens, networking and server-side support. These typically don't don't need big performance (unless you're talking large multi-player) and just need to banged out. For these parts, Fantom is ideal.
go4
would be in a better position to answer that, given his fan3d - Fantom Graphics System (Admission - I've not looked at it yet...) But as long as you can tap into DirectX / OpenGL then why not?One thing I keep meaning to do is try getting Gundam to run on the .NET platform. For if you want to go 3D on Windoze machines, I'd imagine native .NET bindings would be the way forward.