snõwkit dev log #4 (data flow)

sign up sign in

I've been breaking everything further, continuing to finalize the underlying snõw APIs. More to talk about, more dev logs.

previous editions
- #1 “assets”
- #2 “history”
- #3 “modularity”
- #4 “data flow”


Short changelist

As mentioned before, but a bit bigger this time:

These changes will only affect you if you are using the snow api's or types directly.

The code changes shouldn't affect luxe user code otherwise.

These are the changes that require some updating if using the raw api's:

  • ImageInfo.data => ImageInfo.pixels
  • AudioInfo.data.bytes => AudioInfo.data.samples
  • snow.io.typedarray => snow.api.buffers
  • snow.utils.* => snow.api.*
  • tga assets on web require the snow_web_tga flag if used, just like snow_web_psd was there.
  • removed strict and related list stuff (see below)
  • Asset types redone, removed audio api endpoint from assets
  • fixed completion/display/other bugs short term by removing spaces in macros in the hxml output, and trying to shield the modules a bit from the display code

Structurizing

The code layout changes from dev log #3 were the first step in finishing up the structure for the 1.0 beta and beyond. Just to clarify something, a few people were asking questions about this affecting their usage, especially on luxe side, so I wanted to add: they don't.

The code changes described here and the previous dev log are purely informational, as it's always good to understand the tools that you use, even if you never have to dig down that deep it's still valuable to know. These dev logs are for that purpose, so even if taken in passing, you're more equipped and can understand the reasoning that goes into these things.

Anyway - One thing that I was trying to wrangle before was the separation between user facing code and internal (core/system/module) code, i.e - you don't usually creating new instances of the internal audio system, you interact with it via the snow api. But you almost always would be interacting with say a typed array or a Promise, so I wanted to make this distinction clear in the code layout before getting any further.

Now, along with the last dev log changes (separating the differences for internal code) there were a few api's that are essential, but don't fit in the internal code, because they're user facing. A lot of these were put into the utils/ folder along the way, because the utils folder existed, without good reason.

simplifying

In the end, snow is not going to have a lot of code. This is by design. It's not going to include tertiary code that a few apps might need - it's going to only ever include the code that all apps using it require. This means that the folder structure doesn't have to worry about the future adding a lot of change either.

When considered, this makes the separation easier to deal with - it's easy to simplify. Now there is the following code layout under the hood :

  • core/system/modules (dev log 3)
  • Root / Entry point (Snow, App)
  • Types
  • API

So this literally looks that way in code:

The api folder is everything that is user facing API (in the general use of the word, i.e application program/ming/mers interface). Want to create a Promise? import snow.api.Promise; Everything that you can and would interact with is going to be inside this api folder. All the internal code is in core/system/modules, and all the types are defined separately.

The types folder is separate because of all the snow types being defined inside types/Types.hx. A Haxe module (read: file), if it multiple classes, types, enums, abstracts and so on inside, will share the same package with all of the sub types. This makes sense, because they aren't definining sub packages from inside the file, so the types are put alongside each other.

That just means if Types.hx was inside the root folder, it would generate all the types in the snow package, which makes a lot of noise in the snow package. This neat separation in the documentation index, is because of the types folder being separated:

Finally, the root and entry point code. snow works around the concept of bootstrapping, it starts up, and then pulls your app up. In luxe, snow starts the luxe core, which bootstraps your app in turn. In this regard, the root entry point code of "where do I start looking" as far as code design goes is here, right in the root folder.

That's about all there is to it, and I don't plan on changing this considerably again. It always feels good to land somewhere clean, clear and simple though!

About that luxe bootstrap thing...

This is something I haven't yet made too clear I don't think, so let's sort that out.
Because your luxe app is a snow app at heart, you have full access to the snow api's from any luxe app.

Dev log 3 changes actually included an addition to the Luxe api: Luxe.snow (previously accessed via Luxe.core.app). This is how you get at the snow api directly, because there are many reasons (especially later) where you'll want to.

Say for example, you wanted to access the snow default Window instance for your luxe game - in snow, this would be the app.window api from inside the snow.App class. In a luxe, the app portion is replaced by Luxe.snow, making it Luxe.snow.window. All other snow api's work the same way.

The important take away here though - I'd like to reduce redundancy between snow/luxe api. snow is meant to be doing all the work up to a point, and luxe is meant to do the stuff above that. Currently there are a few things luxe has that are redundant, which came from the lineage before snow. These were a side effect of iteration and will be culled in the near future. It won't change much about your using facing api, none that wasn't already discussed (in dev log 1, fetch/load).

The best example of this which you'll see change in the next few days, is the Resource types. TextResource, JSONResource - these types were solving a different problem from a previous time. They are no longer needed, and the new snow api's in this push (and the next few changes) will finalize luxe alpha-2.0, making them obsolete. In place of these, the snow AssetText, AssetJSON etc will be used directly instead. Another good example is the luxe Audio API, it has the same end points as the snow audio api but also implements some of the functionality, not all of the api is a proxy to snow like it should be, it will be align itself better too.

Strict assets/list things changed

This dev log includes a clean up of the asset system as well. Previously, there was this idea of strict asset query, and the list of assets was stored with a bit of meta data, and other various wacky legacy behavior. None of this provided any real purpose in the new code, as it was born from structures of old that have been left behind.

More importantly, they were trying to solve things that the api shouldn't, and trying to fulfill multiple purposes under the same place. Nope, not good, gone!

All this means is that there is no need to worry about the strict flag thing on snow asset api, just load stuff. If you require a strict set of files that are allowed this is gonna have to be in the application level anyway, and snow checking the list arbitrarily without knowledge of the application code was pointless.

What hasn't changed - the list that flow outputs (the file manifest) is still read automatically if it exists, and is still stored under assets.list for convenience. The difference is that it's stored as Array<String> and that's it - easy.

General asset changes

The Asset API changed to align with the data flow api and, more specifically, aligned itself better with the structure I want in going forward. The snow api endpoints like app.assets.image are doing a bunch of things for you to pull a file up and return the image info node. There were a few things to resolve here, that have been resolved in this push.

Previously, the state of an asset wasn't too clear - the asset itself would return a valid instance, even if the contents never loaded. The "asset value" i.e the image info would be null, and the onload handler would be called with null, so you'd know and be able to react.

The new promise based api makes things easy, you always get a promise in return (where it makes sense) and you can account for errors and load completion in a consistent way.

The second problem on top of that was related to some portions of the implementation being obscured by the higher level api. I don't like to design or use api's that do this - that's why you have full access to everything I do from the higher level, at any time. The new api is better aligned to give you access to manually doing things if you ever needed to.

This also meant just unifying the location of these calls for the different asset types, and removing the calls from the asset api that didn't fit (i.e audio). The audio processing api's that are too specific (like on native) and are still available at the module level, as dev log 3 explained, and the audio system handles the sounds which are not done in the same fashion as other assets right now.

Lastly, the way web handled textures from bytes was using the format library png code, which added a lot of weight for something the browser can do for us. It also only does png, which was only used for the luxe debug console and default font. I took all the format code out and used the browser apis, so the web target now has parity with the native targets on the image formats via bytes, with a lot less code as well.

All told, there are a few things to tweak but overall a much simpler, cleaner codebase going forward.

Data flow!

Anyhow! This dev log is supposed to be about data flow! What does that even mean?

It's quite a literal term here, it's about the flow of data from one point to another. Programming is all about this, transforming data. An app reads a text document as bytes, processes it as UTF-8, displays it to you.

One of the things I was looking for an elegant solution for in the new luxe asset api was dealing with assets and resources that aren't sourced from the "internal" methods, which was mentioned in dev log 1 (about loading assets from a packed file). While experimenting, I stepped back and looked at the fundamental task, taking data and transforming it.

There are two stages at work: The data is provided to the app, from the file reading handler. Then it goes through a processing stage, which returns a final result. This led to what is the most-obvious-in-hindsight solution - data flows from a provider, through a processor and comes out the other side.

Consider on the native targets, for a texture, the image files are read from the drive, they are decoded (say from a PNG compressed format) to raw pixels, and then the raw pixels are returned along with some other information about the image (like the width/height and color depth).

On the web target, this is handled drastically differently - the image is fetched via http request, assigned to an <img> node. Once loaded, it's drawn via 2D canvas context, so it's pixels can get grabbed out. These pixels are grabbed from the context, and then returned back to you, so the browser does the decoding indirectly.

So considering these two very different approaches, how do we unify the API to make sense for both? provider and processor, data flow.

A contrived example

Let's try an easy example. A string is provided as base64. It does not matter where the string is sourced from. Could be a file, could be a network request, could be a packed bundle, could be randomly generated.

All of these cases fall under the simple notion of the new data flow api.
Once the string arrives from the provider, we would want to decode it back to a plain text string - and deliver this to the user.

provider => processor => result

Provide a base64 encoded string:

var provider = function(_app:Snow, _id:String) {  
    var str = 'hello $_id';
    var bytes = haxe.io.Bytes.ofString(str);
    var base64 = haxe.crypto.Base64.encode(bytes);
    trace('provider gave: $base64');
    return Promise.resolve(base64);
}

Process that string, decode the base64:

var processor = function(_app:Snow, _id:String, _data:String) {  
    trace('processor got: $_data');
    var result = haxe.crypto.Base64.decode(_data);
    return Promise.resolve(result);
}

And how do we use these?

var load = app.io.data_flow('fake.file', processor, provider);

load.then(function(data:String) {  
    trace('load got: $data');
})

And the logs:

provider gave: aGVsbG8gZmFrZS5maWxl  
processor got: aGVsbG8gZmFrZS5maWxl  
load got: hello fake.file  

all the way down

The new asset api's in snow use this everywhere now. On native, images are provided by the C++ layer, and not processed. JSON files use the default provider (the cross platform file read api's in snow), and a simple default JSON processor. All the Asset types include where it makes sense, a default provider, and a default processor as static methods, and it makes use of these to do the work.

You'll also notice that the functions return a Promise (mentioned in dev log 1), which means that the provider and processor are/can be async, so the data sources or processor could be anything from anywhere. This also makes handling the failure/errors much clearer than before. In the case of the default providers and processors, they always would be dealing with the common data transport type (dev log 1!), the typed arrays, and usually the result relates to their asset (json data flow returns Dynamic, text data flow returns String, etc).

luxe level

Now that the underlying snow asset api's are pretty close to final, although there are some really small things I might tweak when I see what the outcomes are in luxe code, the next step is to actually get luxe using these new types and remove all the dead code from the Resource days. This isn't in yet, but will be soon. But as mentioned above, you can tinker with the data flow via Luxe.snow.io.data_flow if you wanted to so long.

all the way done

That's about all to mention for now, as mentioned in dev log 3, I want to wrap up these asset changes real quick in snow + luxe and the bulk of that is now done. That gives us enough time to prepare things for Ludumdare coming up, and closes off alpha-2.0 in quite a few ways, moving some of the tasks to a later focus sheet.

The code changes in this push also removed some of the things I mentioned that needed addressing, like the web audio code no longer has it's hands in the core code and similar.

Aside from some pretty big things on the native side in snow, it's main api is pretty close to the point where loose ends can be tied off and it would be ready for beta. However I will keep the alpha status at least until luxe is done with it's major systems (rendering, assets, etc) that rely heavily on it, because there could be things needed or adjusted again along the way.

community

The http://snowkit.org/chat is always open, and you can find news on http://snowkit.org and on twitter at http://twitter.com/snowkitorg

There's a lot of neat stuff users are making, and thanks to the devs in the community that shared their (excellent) posts on the community site. They are great reads, be sure to check them out. And don't forget to share your work, it's what the site is for.

Here's some of the tweets since the last dev log, and don't forget the haxe roundups often include droves of interesting haxe projects:

Tags
snowkitdevlogsnowkitdev