|
Re: Reducing Function Times
[Re: jumpman]
#481239
08/15/20 08:58
08/15/20 08:58
|
Joined: Jun 2007
Posts: 1,337 Hiporope and its pain
txesmi
Serious User
|
Serious User
Joined: Jun 2007
Posts: 1,337
Hiporope and its pain
|
Why does while(1) and wait(1); cause so many problems for lots of entities in their own loop? They do not really cause so many. It is just a little bit slower than calling all the functions by yourself. I guess the engine probably checks something or other during the execution of the functions in the scheduler and makes it slower than raw function calls, but that is the thing of been a versatile engine, I think. A single c_move call spends many times the time taken by the scheduler for calling the action. Same happens with ent_animate. Both are pretty intensive functions and it is necessary to reduce their cost as much as possible. Building a function scheduler without taking care of these both functions is futile.
|
|
|
Re: Reducing Function Times
[Re: Dooley]
#481240
08/15/20 09:16
08/15/20 09:16
|
Joined: May 2009
Posts: 5,370 Caucasus
3run
Senior Expert
|
Senior Expert
Joined: May 2009
Posts: 5,370
Caucasus
|
I can only quote Superku, as I can't really say better than he already did: Having one game loop is the way to go IMO, so as long as your project isn't that complex already change it. wait(1) is rather "slow" and you have no real influence on when functions are executed. proc_mode (in particular PROC_GLOBAL) is a game and project killer, leading to seemingly random crashes as it affects all kinds of functions you don't want it to have an impact on. Example: ///////////////////////////////
#include <acknex.h>
#include <default.c>
///////////////////////////////
void projectile()
{
my.skill1 = 128;
my.pan = random(20)-10;
my.tilt = random(20)-10;
while(my.skill1 > 0)
{
c_move(me,vector(16*time_step,0,0),nullvector,0);
my.skill1 -= time_step;
VECTOR temp;
vec_set(temp,my.x);
if(vec_to_screen(temp,camera)) draw_text(str_printf(NULL,"%d",(int)proc_mode),temp.x,temp.y,COLOR_RED);
wait(1);
}
ptr_remove(me);
}
void spawnProjectile()
{
proc_mode = PROC_GLOBAL;
wait(1); // <- doesn't help as proc_mode is restored after wait
ent_create(CUBE_MDL,vector(0,random(16)-8,0),projectile);
}
void reload()
{
level_load(NULL);
}
void main()
{
fps_max = 60;
video_mode = 10;
level_load(NULL);
on_mouse_left = spawnProjectile; // press left mouse button a few times,
on_mouse_right = reload; // then the right mouse button to crash the game
} What I've done for the past few years for new projects was to use on_frame, like this: ///////////////////////////////
#include <acknex.h>
#include <default.c>
// header files
#include "player.h"
// implementation:
#include "player.c"
///////////////////////////////
void mainFrameEvent()
{
input update function;
if(gameMode == GAME_MODE_PLAY)
{
objectsUpdate();
enemiesUpdate();
playerUpdate();
}
if(gameMode == other modes) { ... }
}
void main()
{
fps_max = 60;
level_load(NULL);
...
on_frame = mainFrameEvent;
} This way you have complete control over what gets executed when and how. You could for example freeze all enemies or projectiles in the game with a simple if(variable) check, while allowing the player to move freely. You only have to let's say create a list of objects after level_load (on_level_load or what it's called) and free that list on or before level change. I still use wait(1) entity loops here and there but just for some level decoration/ dynamic objects which don't have an actual influence on gameplay. And Sorry, but you are wrong. A big project is super tough to manage and debug when using wait. The initial setup is a little easier but that's about it, no other advantages, only disadvantages. Btw. 10000 waits eat up 2ms of performance already on a 6700k. You'd have to do one hell of an optimization to save 2ms normally, or you could just NOT use wait. And also a test results performed by txesmi in the past: The only way of speaking about the offtopic is with numbers.
-----------------------------------------
| a wait per entity | own scheduler |
| 1 byte stack | 1 byte stack |
-----------------------------------------
| a wait per entity | own scheduler |
| 128 bytes stack | 128 bytes stack |
-----------------------------------------
Notice that the stack memory size has also its impact in the difference. #include <acknex.h>
#include <default.c>
#define ENT_COUNT 10000
#define STACK_SIZE 1
#define COMPLEXITY 1
ENTITY *ents[ENT_COUNT];
action actWait() {
BYTE _n[STACK_SIZE];
while(1) {
int _i = 0;
for(; _i<COMPLEXITY; _i+=1)
_n[random(STACK_SIZE)] = random(256);
wait(1);
}
}
var actList(ENTITY *_ent) {
BYTE _n[STACK_SIZE];
int _i = 0;
for(; _i<COMPLEXITY; _i+=1)
_n[random(STACK_SIZE)] = random(256);
return -1;
}
void entLoop () {
while(!key_esc) {
wait(1);
ENTITY **_ent = ents;
ENTITY **_entLast = _ent + ENT_COUNT;
for(; _ent<_entLast; _ent++) {
if(*_ent == NULL)
continue;
var _result = actList(*_ent);
if(_result == 0)
continue;
switch(_result) {
case 1:
ent_remove(*_ent);
*_ent = NULL;
break;
case 2:
ent_remove(*_ent);
*_ent = NULL;
break;
case 3:
ent_remove(*_ent);
*_ent = NULL;
break;
case 4:
ent_remove(*_ent);
*_ent = NULL;
break;
case 5:
ent_remove(*_ent);
*_ent = NULL;
break;
case 6:
ent_remove(*_ent);
*_ent = NULL;
break;
case 7:
ent_remove(*_ent);
*_ent = NULL;
break;
case 8:
ent_remove(*_ent);
*_ent = NULL;
break;
case 9:
ent_remove(*_ent);
*_ent = NULL;
break;
case 10:
ent_remove(*_ent);
*_ent = NULL;
break;
case 11:
ent_remove(*_ent);
*_ent = NULL;
break;
case 12:
ent_remove(*_ent);
*_ent = NULL;
break;
case 13:
ent_remove(*_ent);
*_ent = NULL;
break;
case 14:
ent_remove(*_ent);
*_ent = NULL;
break;
case 15:
ent_remove(*_ent);
*_ent = NULL;
break;
case 16:
ent_remove(*_ent);
*_ent = NULL;
break;
case 17:
ent_remove(*_ent);
*_ent = NULL;
break;
case 18:
ent_remove(*_ent);
*_ent = NULL;
break;
case 19:
ent_remove(*_ent);
*_ent = NULL;
break;
default:
break;
}
}
}
}
void main () {
max_entities = ENT_COUNT;
void _act ();
_act = actWait;
// _act = actList;
level_load("");
def_debug();
int _i = 0;
for(; _i<ENT_COUNT; _i+=1)
ents[_i] = ent_create(SPHERE_MDL, vector(0, 0, -1000), _act);
if(_act == actList)
entLoop();
}
I gived a bit of complexity to the scheduler loop so it can be considered a complete flux manager. The numbers speak by themself. It is clear it gains performance with a single while loop but it is not that much. Take into account that we are speaking about a difference of 3/10000 ms/ent: the time taken by few operations. Bad programming practices will waste more time. Salud! Greets!
|
|
|
|