decoupling logic and render

BlitzMax Forums/BlitzMax Programming/decoupling logic and render

Cruis.In(Posted 2013) [#1]
Hey guys I have been pouring over lots of timing methods and implementations and the like, and one thing I am not quite clear on is the decoupling.

So in a game which has Update which updates the x&y movements of objects this is your logic and anything with inside a suitably named Render or draw method/function is your rendering.

So by decoupling, do I have to call all the Updates across all my types into one Function in the main loop placing that before a Render function calling all the rendering from all the classes?

Or is it decoupled so long as I do not update logic in the same function/method as I do rendering?


GNS(Posted 2013) [#2]
I have both ProcessTick() and Render() methods in my objects.

ProcessTick() handles all logic functions for that object (setting x/y positions, etc.). ProcessTick() runs at a fixed interval (i.e. 60 times per second or every ~16 milliseconds). ProcessTick() could also be named Update() or RunLogic() or whatever.

Render() handles all drawing functions for the object (calls to SetColor()/SetAlpha()/DrawImage(), etc.). Render() runs as fast as the player's GPU can draw the screen (potentially thousands of times per second). Render() could also be named Draw() or whatever.

In your main game loop you'll typically run a logic loop once every X milliseconds and then, in between that time, run multiple render loops. As an example, this is my game loop:

Repeat
	Game.currentTickTime = MilliSecs() ' need to replace with hifi timer?
	
	While ((Game.currentTickTime - Game.lastTickTime) > Game.TICK_TIME_MS)
		Game.ProcessTick()

		Game.lastTickTime :+ Game.TICK_TIME_MS
	Wend

	Game.Render()
Forever


In the above code, the 'Game' object is responsible for forwarding ProcessTick() and Render() calls down the chain to other, more specialized object managers (e.g. Game.ProcessTick() will call GUIManager.ProcessTick(), InputManager.ProcessTick(), etc.).


Cruis.In(Posted 2013) [#3]
Thanks for the info I have some more queries. Going to throw in some more variables here.

My gfx card is pretty high spec and I'm prototypinga 2d spacey game, , without vsync the frames are around 600 fps. Then I create 150 ships graphics on screen. The frame now drops below 60 with that amount, is it possible then to increase/optimize that performance drop by decoupling the logic calls from the render as you did above? By updating the logic only once per loop or frame and leaving the render to update as often as possible?

How does decoupling the logic/render affect delta timing?


GNS(Posted 2013) [#4]
The main advantage to decoupling the logic from rendering is that it allows you to perform logic updates at a stable, predictable rate. This is important for certain things (like physics solvers). With something like delta timing your update rate is tied to your render rate, both of which vary from frame to frame.

There's a great article about the different types of game loops here: http://www.koonsolo.com/news/dewitters-gameloop/

As for performance, you probably won't see much benefit from switching to a fixed logic rate. 150 ships doesn't sound like much, though. Does each ship have a unique image?


Cruis.In(Posted 2013) [#5]
Each ship have a unique image? No

I switched to a fixed logic rate, it was kind of cool, I put all the logic updates from the types in the logic loop and the renders outside the logic loop






game timing was exactly as I had left it, I put the wait time at 16, thats 16ms so that's good right? = to 60?

I turned off vsync and though the frames were over 600, it still ran nice and smooth as though I had on vsync. Nothing was changed.

But I get the slow down far sooner, if I put just 55-60 ships on screen as opposed to my 128 before I did the logic separation.


GNS(Posted 2013) [#6]
What happens if you only perform 30 updates per second (waittime = 33.333)? Does the framerate increase?


Cruis.In(Posted 2013) [#7]
it sure does! just a sec


Cruis.In(Posted 2013) [#8]
With waittime at 33.333 , it slows down below 60 begining at about 118 ships, almost what it was before I decoupled. So not much advantage eh.

can delta timing be applied with separated logic?


Cruis.In(Posted 2013) [#9]
note that this slow down occurs whether the ships are on the viewing screen or not.

once it gets up to 100+ created. whether in my locale or not, which leads me to think its definitely the logic boggling down the game, and therefore my logic needs serious optimizing?

Or is 128 objects a lot. Doesn't seem so! Or is there something to be done about objects which have been created but which cannot be seen right now?

Also since I separated the logic and set it to 33.333 and got near the same result in fps when I created about 120, which is quite similar to the time it slows down before I decoupled the logic, it is safe to say I can keep the original implementation while hunting down the bottlenecking, and checking if I've improved anything by creating the amount of ship objects as I go to see if performance increases?


GNS(Posted 2013) [#10]
Does that trend continue as you increase the waittime? If you only do 10 updates per second (waittime = 100) or 5 updates per second (waittime = 200) what happens?

Seeing a framerate increase as a result of doing less logic updates per second typically points to your game being CPU-bound. I'd start looking at optimizing whatever is running during each Update() call or perhaps spreading the workload over multiple ticks (e.g. only update half of the A.I. bots each tick).


Cruis.In(Posted 2013) [#11]
I see, does it also help to know that when I get the stutter the cpu usage is still only at 30%? Does that still indicate a bottleneck in the code? I would have thought the cpu would be near 100.


Cruis.In(Posted 2013) [#12]
I am running in windowed mode btw


Cruis.In(Posted 2013) [#13]
at 200 waittimer, the fps isn't going much beyond 600, but it goes down far less, i can add hundreds of ship objects. However at 200 wait timer which is 5 updates a second, I can't 'play' much.


Jesse(Posted 2013) [#14]
did you try running it on release mode?
how big are your images?
it might be using all of your video memory(not video ram), and it will start moving files from the video area to video ram. which will cause slowdown of graphics display.


GNS(Posted 2013) [#15]
Definitely sounds like it's time to optimize things. You'll probably need to pinpoint exactly what's causing the slowdown by benchmarking/profiling the code somehow.


Cruis.In(Posted 2013) [#16]
I want to thank you very much for helping me....

I just created 1000 objects, and kept going until I couldn't create anymore lol........... I found my bottleneck

There was a drawtext in the enemy update...for all enemies in the list 'drawtext' the text was drawing the amount of ship objects created. As held in a countlist variable which used countlist.

I guess it was layering over like hundreds of drawtext commands.....plus constantly drawing them, i have to assume then that drawtext is a VERY slow command. Since apparently I can have annd draw thousands of objects on screen at once with no drop in frame rate, but with the drawtext there, it bogs at 150+

When I noticed the slowdown I was playing around with various timing methods, and believing my own wasn't any good or suitable in the long run, I started doing these tests. So then I created the thread to help me understand the various methods particularly what is meant by separation.

I guess I got two things with one stone, I do understand far more about the timing and separation than I did before, great to learn for myself that stuff, and I found and removed something which was slowing me down, and I realized in the end it was not related to my timing.

I would have removed the drawtext in the end, it is only there for debugging purposes. But perhaps I should put all my debugging text in the debug function instead of throwing it in where I feel like just to see a number quickly.

what do you think?


Cruis.In(Posted 2013) [#17]
This could cause a slow down too right? suppose if I put 1000 ships on screen , then launch a torpedo whereby in my collision function I am checking for collision against each of those ships, from the time I launch a torpedo it begins checking for collision between it and each of those 1000 ships until the torpedo is removed by age...

Because that was my next test, 1000 ships and fired a torpedo. Frames drop bad, and the frames dont even drop when I have created 2000 ship objects on screen.


GNS(Posted 2013) [#18]
DrawText can be slow, especially if it's being called hundreds of times per frame. It has a particularly bad way of rendering glyphs. A custom bitmap font system that renders text from a single texture atlas via DrawSubImageRect() would be better.

Collision will also be a major bottleneck if it's not handled properly. In the example you posted above, you'd probably want to do multiple 'early-out' checks of increasing complexity before actually testing for pixel-perfect collision. Here's some pseudo-code to illustrate:

for each ship in the game
	' check distance of each ship to torpedo, if they are far enough away to never be in
	' danger of being hit, move on to the next ship
	if (ship distance to torpedo) > "safe distance"
		continue
	
	' if the ship is moving away from the torpedo, or at an angle that ensures it will
	' never be hit, then move on to the next ship
	if (ship is moving away (or perpendicular to) the torpedo)
		continue

	' perform a simple distance check to see if the torpedo is within collision distance
	' if it is then we can perform a more accurate collision test to see exactly where/if
	' the torpedo is going to hit
	' this could also be combined with the first test above
	if (ship distance to torpedo) <= "max size of ship"
		
		' now perform the more costly pixel-perfect collision test
		if (ship collides with torpedo) then do something
	endif
next



Banshee(Posted 2013) [#19]
Excuse me if I missed it as I'm on my mobile ATM, but the purpose of decoupling is not to magically make everything faster. Unless you use threads...

Also, you don't have to run the entire logic cycle every update. I have 1000 of units moving in my own game in dev by being very selective how often I run the slow stuff.


Cruis.In(Posted 2013) [#20]
I think threads would be a new area for me again :)

by the slow stuff you mean stuff which x y do not move around much? I guess the same for fixed stuff too.

So you put some of your slower stuff in a separate loop and lower the update time ? So multiple logic loops?

@GNS strangely enough I had immediately thought to do that when thinking on the collision issue. Especially since pixel perfect collision will be being tested whether the objects are thousands upon thousands of units away or not.

So I guess in the for each in loop comparing the list of projectiles and ships I can measure the distance between each ship and each projectile.

Any other tips on eliminating bottle necks, I mean from avoiding particular bad practices and traps, or is that a whole nother thread? :)


ima747(Posted 2013) [#21]
A couple things that might be helpful

When checking your logic always check for recursive things, and redundant list scans. e.g. if your ship logic looks at every other ship from every single ship,then every pass you're iterating over ships(squared) so each ship will cause exponential scanning growth. May be required, or may be an easy optimization depending on what you're doing obviously.

Think about what each object has to do given some easy to calculate factors. You may be able to use that to create simplified branching logic that could greatly speed up the game in certain situations. E.g. if when a ship is more than 300 units away from the player does it really need to think about it's weapons? can it just try to move towards the player in that instance? or better yet if it's far enough away can it be ignored all together. that is really common in infinite space games, generally on screen is full logic, off screen is logic to try to make it get on the screen and beyond a certain point is just ignored. further you can simplify those "distance" checks by cutting the world space into a grid and only even processing distance on grid units within a certain range of the player meaning everything outside just gets skipped. you can then optimize *that* by actually sorting the ships by the grid unit they're in so you literally only check the ships in grids that might be relevant, and then it's just a simple check to have them shuffle closer to the player. you can effectively cut out HUGE expensive code passes by finding a good way to ignore things that aren't interesting at the moment.

Regarding threading: I'm a huge fan of threading, but as with all my mentions of threading a) it's tricky b) prone to bugs c) there are a few inherent glitches with it's implementation in bmax and d) it's always best when it's designed in from the beginning rather than things adapted for it. That is said as a friendly warning, not to discourage you, just be prepared for some real head scratchers :) personally in your position I would look into optimization first since you're already finding bottlenecks and threading won't solve those, just move them and make them harder to identify moving forward.


Cruis.In(Posted 2013) [#22]
thank for the suggestions ima747

What if some objects are on screen close to the player and others are far off? Once I call to update one set all will update, I guess this is where I separate the lists containing enemy ships into sensible different lists then?

As for the loop, if I am interpreting you correctly, I should have several subdivisions each containing only the required logic for that particular set of prevailing circumstances. Then based on the parameters, each loop will only activate once their parameter is met.


Main
while // Main loop

while objects near // 1st condition checker
*do all logic*
wend

while objects far // 2nd condition
*do necessary logic only*
wend

RenderALL


Wend // end of main loop

So the second loop doesn't update any other logic because its far off.


While I am understanding it a lot I must be missing the associated techniques with doing it. Since again let's say I am in Home space, and home space is about 10k * 10k units. Five ships patrolling home space are near to me, and the rest are not, but each of the ships patrolling home space is in one list. If the main logic loop activates, it'll be processing even the far ones.

Or wait, is the solution then to check each individually...hmm so use a for eachin loop to iterate through the enemy shiplist, then IF NEAR, with near being less than X units, let each enemy ship object call its own update logic method.

And that for/next could go around the update for the enemy ships only, not anything else. So it'll only update the individual objects near to me.

Am I thinking this through right?