sphere-group / pegasus

The Pegasus API for Sphere 2.0
BSD 2-Clause "Simplified" License
1 stars 0 forks source link

Main loop and staging #7

Open joskuijpers opened 10 years ago

joskuijpers commented 10 years ago

There is a very important part of the API I have not been able to wrap my head around: where the actual game code goes.

We talked about a stage system for different game stages. I think that would be part of the problem stated.

Well, I have no clue. :smile:

Radnen commented 10 years ago

I'm guessing that there is a global stage handler and it's active so long as it has stages in it.

Example:

var stage = new Sphere.Stage()
.enter = function() { }
.leave = function() { }
.load/init = function() { };

Sphere.stages.add(stage);

I've been using angularjs so dependency injection could also be nice.

new Sphere.Stage("mystage");
.enter = function() { }
.leave = function() { }
.load/init = function() { };

Sphere.stages.add("mystage");
joskuijpers commented 10 years ago

Alright, but where would that be called? Where would the first stage be created? And does a stage have some .loop() ?

joskuijpers commented 10 years ago

@Radnen Could you look into such stage system? Designing it for Pegasus :smile:

Radnen commented 10 years ago

I added a basic stage and stage manager in commit 0fa8a42ddac6bf07dcce1f036adc58e248eb78a0. Its only bare bones and does not have dependency injection.

Currently the API has it so there is a single loop method in the stage. From my experience creating games across multiple libraries this is not so good. In Sphere you could get by with that but in reality most games separate their update and render loops. Game logic should happen in the update loop while graphics are drawn in the render loop.

This is essential when we start asking questions like what kind of timestep system will we use? Will the games be drawn at a fixed fps? Use a clock - aka variable timestep (my favorite)? And it's useful to separate update and render so that things render as they should at 60fps despite what timestep method you use while the logic can update as fast as it can. This is seen particularly useful when games dip below 60 fps but still have accurate physics etc.

If update and render are separated, then when we pause a stage we should allow the ability to pause one, the other or both. Sometimes it's useful when displaying menus to pause the logic of all previous stages or scenes while the topmost stage is active. Then in the case of a HUD it may be that nothing on that scene is paused. However, there is generally no reason to pause just the render portion, but just in case I think it's okay to still expose a method of doing that.

Any thoughts on the api before I add this functionality?

joskuijpers commented 10 years ago

First of all, thanks! Second, a style-thing: I recently put all prototype methods that should not be overridden inside the class function: is cleaner and makes them readonly.

Also, do you think we could merge stage.js and stagemanager.js? If stage.js only exports Stage as a class so that var stage = require("stage"); new stage.Stage(), then we could aswell put the Stage class in the stagemanager file? (And rename that file to stage/stages).

What about making the this.loaded in Stage readonly (specs only, in actual implementation one could use .defineProperty with the writable:no key, or some other way). Then we have nicely stage.loaded instead of stage.isLoaded().

Then, third, about your splitting of update and render loop: Excellent idea! On the mac, I happen to have my render loop limited to the display refresh rate to prevent flickering or over-use of the processor. And I get calls like 'update to animation time xxx': so that if there is lag, the game knows what needs to be updated to. I think this is what you refer to? Having a stable render loop and some time-based updateLoop(time)?

Radnen commented 10 years ago

Second, a style-thing: I recently put all prototype methods that should not be overridden inside the class function: is cleaner and makes them readonly.

Putting methods into the constructor can cause weird memory issues. I ran into this when testing my Link.js library. Therefore I personally adopted the method of always using prototype. I'm not entirely convinced V8 will take care of .defineProperty calls in the constructor and cache/optimize them. Even though you may get more freedom out of it, games I've come to learn need every ounce of speed we can squeeze out of the engine. I learned this the hard way too trying to use defineProperty with Color objects in Jurassic/SSFML.

What about making the this.loaded in Stage readonly (specs only, in actual implementation one could use .defineProperty with the writable:no key, or some other way). Then we have nicely stage.loaded instead of stage.isLoaded().

Hmm, the StageManager has to be able to set that variable. Or there must be a method that somehow sets it and since there's no easy way of forcing people to properly polymorph/inherit functions, it becomes tricky to implement something like that. But you are right the user shouldn't be able to set .loaded at will.

Then, third, about your splitting of update and render loop: Excellent idea! On the mac, I happen to have my render loop limited to the display refresh rate to prevent flickering or over-use of the processor. And I get calls like 'update to animation time xxx': so that > if there is lag, the game knows what needs to be updated to. I think this is what you refer to? Having a stable render loop and some time-based > updateLoop(time)?

Precisely. But I think the update loop can take either a fixed timestep or variable timestep depending on what works best for your game. We either design this into Pegasus or define ways of letting the game developer handle this on their own.

Oh, and I could have put StageManager into stage.js, but I had separated them because of separation of concerns. Personal stage modules may never touch StageManager directly so why expose that to them? When I'm installing a new kitchen sink I don't want to include the stove, microwave and refrigerator too. The stage manager would only be used for higher level debugging or handling of scenes, such as when you first set up the game.

joskuijpers commented 10 years ago

On putting methods within the function braces: remember this is only the API and that the implementation does not matter. What is important is clean code, and that the API is implementable.

When I want functions to be 'protected': only be used by the same module, I hack by adding an underscore to the function so that it shouldn't be used by others. E.g.: var myPrivateVar; this._setMyPrivateVar = function(v) {};

I do understand the separation of concerns, but I think modules are at a higher level. (we are talking SRP here, right?): The stage class represents a stage. StageManager class manages the current stage and the stage stack/queue/whatever. These together make the stage system, which is then the stage module. That is how I think of it. But you might disagree of course.

joskuijpers commented 10 years ago

@Radnen Can you give me a test case or definitive article telling me that defineProperty is a huge performance hit? Because I really like using properties instead of two setX and getX methods. (I used it in the sound module too.). But if you convince me that it sucks ballz in performance, please do tell. I do think however that we either use properties everywhere, or use get+set everywhere to make the API more intuitive.

Btw, you can set normal properties ot be readonly, without using a custom getter. Also, I don't think using defineProperty and {value:10,readonly:true} is any more costly than using this.x = 10, because it becomes the same thing in the engine (AFAIK).

joskuijpers commented 10 years ago

@Radnen Could you write an example using the stage manager? I have a hard time grasping it. And I want to implement it to test the dev experience and to workings, Thanks!

Radnen commented 10 years ago

To your earlier question, defineProperty has no real performance hit: http://jsperf.com/defineproperty-vs-regular-property

To your later question, here is a gist example of a stage example: https://gist.github.com/Radnen/2c1afc0528090ea63d71