optimizing loop

Started by miguel, Tue 15/01/2013 10:10:29

Previous topic - Next topic

miguel

So, I've built a very simple but working little dungeon game.
Enemies detect the hero presence and engage according to distance, fights occur with different stats for every character, etc...
Making it working was a big achievement for me, but now I would like to optimize it.
My question is: how can I stop the engine from removing player control when blocking events happen; I know I am answering my question, I can't if they are blocking. But I've seen it done.

I've tried CC for npc animations with no success and breaking function code into smaller portions also with no success.
Do I require a tile-engine?
What's the concept behind allowing the engine to perform such "arcade" behaviours?

Everything in my game runs in Rep_Execute.
Working on a RON game!!!!!

Crimson Wizard

I am not sure I understand. Do you mean, there are actions that are explicitly blocking, or do you mean that some process runs so long, that game appears hung for a moment?

Also, what is "CC"?

miguel

CC is , (sorry for that), CharacterControl module.
I mean that if a animation takes place it will block the game until it finishes and setting it to non-blocking will (because all my variables allow it) process things very fast.
I know it is the proper behaviour of the engine but I've seen examples where functions run without the player loosing control.
Working on a RON game!!!!!

Crimson Wizard

#3
In this case the topic title is misleading. It is not about optimizing your function (making it faster), but about synchronizing character animations with game process? Well, or something like that, I guess.

If your game is arcade you obviously cannot use blocking animations, at all (except for cutscenes maybe). You must redo your game logic so that animations run in non-blocking fashion. I do not know what you did there and why things are processed too fast, so I can't tell whats wrong exactly.

In general case, every entity (e.g. enemy) remembers its current state, and acts according to its state. It changes state when certain event occurs, or certain time period passes.
Let's take an example. There's one enemy that can walk towards player and attack.

- If enemy is too far, you set him to Chasing state. If nothing else happens, it checks for player position, like, every second, and fixes his destination accordingly. Every time (once a second), he starts Chasing (walking) non-blocking animation.
- If enemy is in Chasing state and comes close to player, you set him to Attack state and start Attack non-blocking animation (like, he swings his sword).
- If enemy is in Attack state, and his (non-blocking) animation finishes, you check if player is still in range, and if he is, then player looses some health. Then enemy is set either to Attack state again (if player is close) or to Chasing state.
In either case all this requires checking enemy state once in a while (higher frequency gives smoother action, but also increases processor load).

So far we examined timed processing. Let's take an example of events. What if player attacks enemy while he is in Chasing state? He then would interrupt chasing state and bring enemy to some "Damaged" or "Hit" state, which will make him to play "Pain" animation (non-blocking!). While this animation is played, enemy cannot switch to Chasing not Attack. So you make this check repeatedly too (see if the animation is finished).
If player continues to deal damage and kills enemy, enemy is set to death state and plays Death animation, after which he is no longer processed and left as a dead body, or just dissapears.


BTW, some time ago I made this:
http://www.mediafire.com/?exxi3y0ddy1
It does not use any blocking functions at all. Probably I could find a source code... I don't remember is it good enough to show though :).

miguel

#4
You are right about the topic title being misleading.
I understand what you're saying. Should I use global variables to set bool states and Rep_Execute to keep checking it?
A "iniciative" function could do the check, like after the non-blocking animation finishes it checks again for who has the iniciative?

Downloaded your demo, would be great to have the code.

Working on a RON game!!!!!

Crimson Wizard

Quote from: miguel on Tue 15/01/2013 12:24:07
Should I use global variables to set bool states and Rep_Execute to keep checking it?
In one of your previous topics I saw you have a struct for enemy? You can just add "state" variable there. I don't think boolean variables for states is a good thing, because you will need a variable for each possible state. It is probably better to have integer, or enum type.
For example:
Code: ags

enum EnemyState
{
  eStateRunning,
  eStateAttacking,
  ...etc
}

Then you declare variable as
Code: ags

EnemyState state;


And, yes, you check it in repeatedly execute function. Also check Character.Animating property - it tells if character is currently playing any animation.

I will try to find the code, but I think there was just a loop which cycled all enemies, containing one big switch which selected what do do depending on their state, and whether the animation is finished or not.

miguel

Got it, I'll dive into it right now.
Working on a RON game!!!!!

miguel

#7
Okay, I defined states for every little bit of what's happening in the game and yes, I've got it running towards what I had in mind.
My issue here is where do I unlockview of the characters after animating? Unlocking it is making the game freeze.

Edit: I'm using on_mouse_click to unlock views now, it's working. Is this the best approach? The only one?
Working on a RON game!!!!!

Crimson Wizard

I found my project... and speaking frankly I am a bit depressed seeing how overcomplicated it is. I was using monkey_05_06's Stack to store enemy data, and now I am not sure it was good solution. Lots of code is only about reading and writing data to string. Today I would probably just use static arrays.
Also, it is WIP, and I made some mistake there that causes an observable bug. I can't fix it right now (may take a time for me to refresh algorythms in memory). :-\ So, not sure if that will be of any use, honestly. But here it is:
http://www.mediafire.com/file/a3nx46dnsfmvcv3/ShootingGallery.7z


Quote from: miguel on Wed 16/01/2013 14:14:21
Okay, I defined states for every little bit of what's happening in the game and yes, I've got it running towards what I had in mind.
My issue here is where do I unlockview of the characters after animating? Unlocking it is making the game freeze.
And why do you need to lock views? Unless character moves, it won't animate by itself anyway.

Quote from: miguel on Wed 16/01/2013 14:14:21
Edit: I'm using on_mouse_click to unlock views now, it's working. Is this the best approach? The only one?
I am confused. I totally do not understand why do you have to unlock views upon mouse click? What are you trying to do?

miguel

Yes, locking the view was a bad mistake due to being doing it for years!
But mouse click is fine to change the view back to the walking cycles. Using changeview after the non-blocking animation would cut it short, no?

Thanks for the files, I'm sure it's a great way to study ags coding.
Working on a RON game!!!!!

Crimson Wizard

Quote from: miguel on Wed 16/01/2013 20:01:26
But mouse click is fine to change the view back to the walking cycles. Using changeview after the non-blocking animation would cut it short, no?
Idea is to check current state in rep_exec function over time.
If you need to call ChangeView right after some animation is finished, you do something like:
Code: ags

// Has non-blocking animation finished?
if (!Char.Animating)
{
   Char.ChangeView...
}

Of course you may need more conditions there, like check what is a current View, or some variables.
By the way, I now recalled that in my game I did not set any "state" variable, but used current character loop as a definition for what is character doing, like attacking, dying etc. Like, loop 10 is melee attack, loop 11 is ranged attack, and so on.

miguel

Crimson, I've just finished examining your works and I find it inspiring...in a Dwarf Fortress sense.
It's really too much information at one go and I'll be going there from now on for sure. It's great to actually look at the stuff I read from you, monkey, khris and the rest. I admire you guys. And thank you for your patience.

P.S: my little dungeon runs fine, not as smooth as your demo (impressive) and I've been adding enemies and chests, keys and doors...It feels nice.
Working on a RON game!!!!!

Crimson Wizard

Quote from: miguel on Wed 16/01/2013 23:07:03
It's really too much information at one go and I'll be going there from now on for sure.
As I said, there's too much extra code, not related to enemy AI. I made a mistake trying to put everything into one module... this should be split into data storage / AI parts. AI functions themselves are not so big or difficult. Also lack of comments... argh! :)
Maybe I'll find some time to improve this project.

miguel

Yup, comments would be a gift from the gods!
Enemy AI is the next step on my list.

Anyway, is it safe to say that (in Rep_Execute) the more I split the code into small chunks the better it runs?

Working on a RON game!!!!!

Crimson Wizard

#14
Quote from: miguel on Thu 17/01/2013 12:24:22
Anyway, is it safe to say that (in Rep_Execute) the more I split the code into small chunks the better it runs?
Is this something that you see in practice?
I would not say anything like that for any programming language, but I remember there was an interesting effect, which I observed in Gepard's "WOO", but could not explain back then. As more characters arrived at the scene, the game started to slow down significantly.
I was helping Gepard to improve the code, and originally there was a HUGE repeatedly execute function, but when I split it into several smaller ones, game ran very smoothly.
I am still unsure of the reason. It may be some bug in the engine, or perhaps I just optimized the algorythm without paying enough attention to how much it improved the game speed.
IIRC Chris Jones mentioned something like this too...

miguel

QuoteIs this something that you see in practice?
Yes, at least to my limited knowledge. Splitting the code into small but objective functions allowed me to run all the stuff happening without the player being blocked. It was my main goal when I started this little exercise. A big rep_execute with dozens of if's would have made me quit half way. Well, if you didn't explain me the ropes I would have quit either way.
I believe that I can brake it even more and will do it.
And of course none of my functions include any complex algorithms, they are but simple while loops and such.
This is what my rep_exe_alws looks like: 

Code: AGS
function repeatedly_execute_always() 
{
  if (gamestart==true) {
  if (mood==free) {
    Label11.Text="free";
    enemydetection();
  }
  if (mood==ataking) {
    Label11.Text="atacking";
    colision1();
  }
  if (mood==fighting) {
    Label11.Text="fighting";
    combat();
  }
  if (mood==taking) {
    badtakedamage();
  }
  if (mood==giving) {
    herotakedamage();
  }
  if (mood==heropushed) {
    hpushed();
  }
  if (mood==banditpushed) {
    bpushed();
  }
erasebadguys();
showinterface();
checkdeath();
kill();
  }
}


There's still room for more function simplification.
All this time I thought that this was the correct way to do it, but
QuoteI would not say anything like that for any programming language
made me realize how specific AGS scripting is.
Working on a RON game!!!!!

Khris

Miguel, splitting rep_exe code into several functions won't prevent blocking. Either code is blocking or it isn't; it doesn't matter whether it is in its own function or not.
I'm fairly surprised about the speed thing though; my guess is that AGS's memory management comes into play here.

miguel

Khris, I wouldn't dare defy your expertise, it's obvious that I now very little of this. I did think I got it working doing it the way I did.
Consequentially my error reminded Crimson of that "speed" enhancement.

Anyway, you do agree that breaking the code into functions allows to organize things better, no? I understand that rep_exe is a chain of events that continuously loop and splitting code into functions and then call them back into the loop is the same thing. But, "my method" should make things slower, right?

Question here: if everything runs inside the loop and all is coded in rep_exe, calling return to stop a function would also break the chain? If done on functions outside rep_exe, it would go back to the following chain function? Would the outcome be exactly the same?
 
I'm learning here, and more questions come up with every change I make to the code.
Working on a RON game!!!!!

Khris

I'm all for breaking things up into tidy chunks, absolutely, and I'm glad that the weird speed difference favors splitting things up. It would be really bad if we had to choose between tidy code and fast execution.
I'm not sure exactly what you're referring to by "my method", if you're talking about calling several functions in rep_exe, it seems it doesn't make things slower, on the contrary.

As for "return", calling it will exit only the current function, not the one that was calling it. If that would be the case, what if you called a function that called a function? Based on what would AGS be supposed to decide how many functions to exit?

What's contributing to this confusion is some people having posted code in the past that made heavy use of return in order to prematurely exit a function. While it is legitimate to use it that way, the original purpose of the return function is returning a value, the "value of the function" as it were.
Here's two examples:
Code: ags
int Sum(int a, int b) {
  return a + b;
}

int Abs(int a) {
  if (a < 0) return -a;
  return a;
}


The first one shows the proper use of return; the function does something to the parameters and returns the outcome, in this case an int. The data type of this outcome is specified by the function type, before the function's name. We're defining a function that returns an int, based on two int parameters.
The second function returns the absolute value. Here, in the case of a being less than 0, the function is exited prematurely, but still returning the correct value.

Whenever you use a function in code, it is executed, then replaced by it's return value. Take a look at this example:
Code: ags
int mySum = 3 + Sum(4, 5);

Here, the return value is used in a mathematical term. It evaluates to 3 + 9.

If we're talking about functions that just do something and don't return anything, like for example UpdateActionBar(), return can be "abused" to exit the function.
Execution will resume in the line after the function call.

Crimson Wizard

Quote from: Khris on Thu 17/01/2013 21:04:34
If we're talking about functions that just do something and don't return anything, like for example UpdateActionBar(), return can be "abused" to exit the function.
There's a code style that suggests using early 'return' instead of taking a different execution branch(es) into 'if' block, like
Code: ags

if (nothing_else_matters)
   return;

lots of code here

instead of
Code: ags

if (!nothing_else_matters)
{
   lots of code here
}

Good or bad, I use that a lot :P.
On other hand, the restriction to only one return may force coder to reorganize function contents, so that there is no long trailing pieces of code under 'ifs'.

SMF spam blocked by CleanTalk