Quote: "The only point I was trying to make with that reference was that from the hardware I started on, I was focused on those tiny efficient details"
I know; and I wish I did too, I admire those people who can entertain me with just 8 bits.
Quote: "It's just a habit of mine to want to gain every extra tick possible."
I had your principle in mind when I first started, I have thousands of constant state integers in my project, and continue to practice this habit as well. But as I go I am seeing that a lot of such optimization has wasted time. But this not an issue for a 1000-5000 liner project or something concise targeting mobile hardware.
I found out that using loads of constants takes longer to manage than using alternative means such as function pointers, hash-tables, UDT arrays, flags, XML, LUA, and string databases. One of many scenarios is LUA needing to check the state and not DBP; another is needing to store states as strings anyway to display visual feedback; another is later on wishing to change functionality without re-compiling 1000s of lines.
I now choose different methods of handling states according to specific needs (Favorite being function pointers, scripts, UDT enumerators and flags); but I tend to default to defining things outside the program; in my real world environment, recompiling to add just one or two new constants is an absolute no no. But for something concise, it is good because compiling only takes less than a minute.
Quote: "Using constants this way, you still have a variable with a meaningful name so you know what it is"
I also personally find it annoying searching through 1000s of constants to remember the name of one of many states. I prefix the name of the constant with namespace; as you did. Which is why 'namespacing' and auto-complete is handy, but only good when auto-complete works.... :-|
Quote: "10 million integer comparison checks take about 20ms, whereas strings took 80ms. That's 4x slower for strings."
A lot states only need checking once every second, some once a minute, for me 4 times per second for each TCP packet update, at most a human is not going to interact with the game more than 10 times per second 99% of the time unless it is an intense Dark Dragon fighting match or an Olympic button bash game; so you'd have to check a good
billion strings to upset your AMD/Intel hamster that way, which is not going to happen.
The few microseconds lost for comparing a string is actually less expensive than for example using a print command instead of a2Text; both commands can display the information to the user, print will get the job done easier and is more productive with concatenation, a2Text runs faster, but we tend to use print because it is handy. So I suppose that's why my gut feeling pushed me to contribute a little feedback about constants being running faster, but not being all that handy in the long run in few situations as mentioned.
Integers are best
A word for newbies, don't even bother with bytes, booleans or words; stick to integers. All my frame-by-frame stuff is integers and references mostly handled by plugins, so if it needs to be performed 60/30/What ever refresh target times per second, stick to integers. Each use of any other non-precision data type is converted to integers for calculation. Booleans take up as much ram as bytes. May as well store up to 32 booleans in one integer using flags.
Quote: "Because in this example we're using an empty string thing$. If the variable matches the target string, it'll take longer to check. And the longer the string, the slower it gets if the characters all match."
With the string comparison, indeed each character of both string operands are compared one by one, to the end of the string I am guessing; so it will be slower. I have done this speed test before when comparing long string comparison Vs multiple short string comparisons for my message system.
So my guess that a medium word string comparison is less than about 0.000001 seconds is more or less confirmed by your experiment.
I also experimented and did not manage to affect my 1300 uncapped FPS on my crappy PC comparing the series "abcdefgh" with itself 100 times per loop; which shows that doing stupid amounts string comparisons will make you lose frames especially when capped; but it is safe to do a moderate amount of real-time script parsing or minor string comparisons if done with reasonable sized strings or done 4 or so times per second.
....
Another thing that bothers me is the use of long Select statements; there is this huge novel of AI script state select case AI_STATE constant set-up in FPS Creator source code; checking through all of those integer comparisons every AI command and comparison in every loop is something that looks like a bottle neck to me, that seems like it would be better as a short circuited branch with 32 bit or perhaps 64 bit flags.
This is the perfect example of where NOT to do string comparisons; although the parsing of the AI script requires string operation.
The main thing is it works; but people say FPSC is slow, maybe - just maybe this contributed to it; possibly Lee Bamber did this for the same principle, good practice?
select aiactionseq(seq).type
`
case AIACTSTATE:
entityelement(e).ai.state=aiactionseq(seq).value
endcase
case AIACTINCSTATE:
entityelement(e).ai.state=entityelement(e).ai.state+aiactionseq(seq).value
endcase
`
case AIACTDESTROY:
rem deactivate and hide entity (beenkilled used by spawner)
entityelement(e).beenkilled=1
entityelement(e).active=0
if obj>0
if entityelement(e).eleprof.physics<>0
gosub _ode_switchoffe
else
set object collision off obj
endif
hide object obj
endif
rem stop any looping sound
gosub _ai_stopentsounds
rem clear entity from col map
tx=entityelement(e).x/25
ty=entityelement(e).y/100
tz=entityelement(e).z/-25
if tx>=0 and ty>=0 and tz>=0
if tx<=viscolx and ty<=viscoly and tz<=viscolz
if viscolmap(tx,ty,tz)=e then viscolmap(tx,ty,tz)=0
endif
endif
endcase
case AIACTSUSPEND:
rem deactivate entity (like a corpse)
entityelement(e).active=0
entityelement(e).beenkilled=1
entityelement(e).logiccount=0 : entityelement(e).logiccountburst=5
rem stop any looping sound
if entityelement(e).firesoundloop>0 then stop sound entityelement(e).firesoundloop
entityelement(e).firesoundloop=0
rem clear entity from col map
tx=entityelement(e).x/25
ty=entityelement(e).y/100
tz=entityelement(e).z/-25
viscolmap(tx,ty,tz)=0
endcase
case AIACTFLOORLOGIC:
entityelement(e).nofloorlogic=1+entityelement(e).y
entityelement(e).nogravity=1
if gmultiplayergame=0
if entityelement(e).eleprof.physics<>0
rem switch off gravity for this object
if obj>0 then ode set gravity obj,0
endif
endif
endcase
case AIACTNOGRAVITY:
entityelement(e).nogravity=1
if gmultiplayergame=0
if entityelement(e).eleprof.physics<>0
rem switch off gravity for this object
if obj>0 then ode set gravity obj,0
endif
endif
endcase
`
case AIACTRUNFPIDEFAULT:
rem leave and run new DEFAULT AI script
trundefaultscriptinternal=1
if aiactionseq(seq).value=0 then trundefaultscript=1+entityelement(e).ai.libinit
if aiactionseq(seq).value=1 then trundefaultscript=1+entityelement(e).ai.libmain
if aiactionseq(seq).value=2 then trundefaultscript=1+entityelement(e).ai.libdestroy
if aiactionseq(seq).value=3 then trundefaultscript=1+entityelement(e).ai.libshoot
conindex=ailist(aiindex).conditionlast
seq=aiaction(actindex).last
exit
endcase
case AIACTRUNFPI:
rem Switch FPI AI Scripts via SPECIFIED FILE
trundefaultscriptinternal=0
trundefaultscript=1+aiactionseq(seq).value
conindex=ailist(aiindex).conditionlast
seq=aiaction(actindex).last
exit
endcase
`
case AIACTMOVEUP:
if obj>0
tmpx#=object position x(obj)
tmpy#=object position y(obj)
tmpz#=object position z(obj)
temp#=(entityelement(e).eleprof.speed/100.0)*aiactionseq(seq).value
entityelement(e).y=tmpy#+temp#
gosub _entity_controlrecalcdist
position object obj,tmpx#,entityelement(e).y,tmpz#
endif
endcase
case AIACTMOVEFORE:
tmpx#=entityelement(e).x : tmpz#=entityelement(e).z
tmp#=(entityelement(e).eleprof.speed/100.0)*aiactionseq(seq).value
entityelement(e).mover.dx=newxvalue(tmpx#,entityelement(e).mover.da,tmp#)
entityelement(e).mover.dz=newzvalue(tmpz#,entityelement(e).mover.da,tmp#)
entityelement(e).mover.dy=entityelement(e).y
entityelement(e).mover.moved=1
entityelement(e).mover.run=0
endcase
case AIACTRUNFORE:
tmpx#=entityelement(e).x : tmpz#=entityelement(e).z
tmp#=(entityelement(e).eleprof.speed/100.0)*aiactionseq(seq).value
entityelement(e).mover.dx=newxvalue(tmpx#,entityelement(e).mover.da,tmp#)
entityelement(e).mover.dz=newzvalue(tmpz#,entityelement(e).mover.da,tmp#)
entityelement(e).mover.dy=entityelement(e).y
entityelement(e).mover.moved=1
entityelement(e).mover.run=1
endcase
case AIACTFREEZE:
entityelement(e).mover.dx=entityelement(e).x
entityelement(e).mover.dy=entityelement(e).y
entityelement(e).mover.dz=entityelement(e).z
entityelement(e).ai.headangle=0.0
entityelement(e).ai.headdestangle=0.0
entityelement(e).ai.waypoint.state=999
entityelement(e).mover.moved=0
entityelement(e).mover.run=0
entityelement(e).strafemode=0
entityelement(e).mover.ix=0
entityelement(e).mover.iz=0
entityelement(e).mover.strafe=0
entityelement(e).mover.stepcount=0
entityelement(e).plrtrailindex=0
endcase
`
case AIACTROTATEY:
tmp#=aiactionseq(seq).value
entityelement(e).mover.da=entityelement(e).mover.da+tmp#
if entityelement(e).mover.da<0 then entityelement(e).mover.da=entityelement(e).mover.da+360
if entityelement(e).mover.da>359 then entityelement(e).mover.da=entityelement(e).mover.da-360
endcase
case AIACTROTATETOPLR:
entityelement(e).ai.headangle=0.0
entityelement(e).ai.headdestangle=0.0
entityelement(e).mover.da=wrapvalue(diffangle#)
endcase
case AIACTROTATEIY:
tmp#=aiactionseq(seq).value
entityelement(e).ry=entityelement(e).ry+tmp#
entityelement(e).mover.da=entityelement(e).ry
if obj>0
yrotate object obj,entityelement(e).ry
endif
endcase
case AIACTNOROTATE:
entityelement(e).norotate=aiactionseq(seq).value
endcase
`
case AIACTRESETHEAD:
entityelement(e).ai.headdestangle=0.0
endcase
case AIACTROTATEHEAD:
tmp#=aiactionseq(seq).value
entityelement(e).ai.headdestangle=tmp#
endcase
case AIACTROTATEHEADRANDOM:
tmp#=(rnd(1)*2)-1
tmp#=rnd(aiactionseq(seq).value)*tmp#
entityelement(e).ai.headdestangle=tmp#
endcase
`
case AIACTFORCEBOUNCE:
if tmpframe=aiactionseq(seq).value=0
entityelement(e).force.ix=0
entityelement(e).force.iz=0
else
entityelement(e).force.ix=entityelement(e).force.ix*-0.5
entityelement(e).force.iz=entityelement(e).force.iz*-0.5
endif
endcase
`
case AIACTSPINRATE:
entityelement(e).spinrate=aiactionseq(seq).value
rem to have spin, must disable physics
if entityelement(e).eleprof.physics<>0
gosub _ode_switchoffe
entityelement(e).eleprof.physics=0
endif
endcase
case AIACTFLOATRATE:
entityelement(e).floatrate=aiactionseq(seq).value
rem to have float, must disable physics
if entityelement(e).eleprof.physics<>0
gosub _ode_switchoffe
entityelement(e).eleprof.physics=0
endif
endcase
`
case AIACTSETFRAME:
tmpframe=aiactionseq(seq).value
tbaseframe=tmpframe : gosub _entity_getactualframestart : tmpframe#=tactualframe
entityelement(e).animframe=tmpframe#
entityelement(e).destanimframe=tmpframe#
entityelement(e).animtime=0
entityelement(e).animdo=-1
if obj>0
stop object obj
set object interpolation obj,100
set object frame obj,tmpframe#
endif
endcase
case AIACTINCFRAME:
tmpframe=aiactionseq(seq).value
tbaseframe=tmpframe : gosub _entity_getactualframefinish : tmpframe#=tactualframe
entityelement(e).destanimframe=tmpframe#
entityelement(e).animtime=0
endcase
case AIACTDECFRAME:
tmpframe=aiactionseq(seq).value
tbaseframe=tmpframe : gosub _entity_getactualframestart : tmpframe#=tactualframe
entityelement(e).destanimframe=tmpframe#
entityelement(e).animtime=0
endcase
case AIACTANIMATE:
tmpframe1=entityelement(e).animdo
tmpframe2=aiactionseq(seq).value
if tmpframe1<>tmpframe2
entityelement(e).animset=1+aiactionseq(seq).value
entityelement(e).destanimframe=0
entityelement(e).animframe=0
entityelement(e).animtime=0
endif
endcase
case AIACTADVFRAME:
tmpframe=aiactionseq(seq).value
tmpframe#=entityelement(e).destanimframe-entityelement(e).animframe
tmpframe#=(tmpframe#/100.0)*tmpframe
entityelement(e).animframe=entityelement(e).animframe+tmpframe#
endcase
`
case AIACTSOUND:
tmpx#=camera position x()
tmpy#=camera position y()
tmpz#=camera position z()
if aiactionseq(seq).value=-1
playinternal3dsound(entityelement(e).soundset,tmpx#,tmpy#,tmpz#)
else
if aiactionseq(seq).value=-2
playinternal3dsound(entityelement(e).soundset1,tmpx#,tmpy#,tmpz#)
else
playinternal3dsound(aiactionseq(seq).value,tmpx#,tmpy#,tmpz#)
endif
endif
broadcast3dsound(tmpx#,tmpy#,tmpz#,100.0)
endcase
case AIACT3DSOUND:
tmpx#=entityelement(e).x
tmpy#=entityelement(e).y
tmpz#=entityelement(e).z
if aiactionseq(seq).value=-1
playinternal3dsound(entityelement(e).soundset,tmpx#,tmpy#,tmpz#)
else
if aiactionseq(seq).value=-2
playinternal3dsound(entityelement(e).soundset1,tmpx#,tmpy#,tmpz#)
else
playinternal3dsound(aiactionseq(seq).value,tmpx#,tmpy#,tmpz#)
endif
endif
broadcast3dsound(tmpx#,tmpy#,tmpz#,100.0)
endcase
case AIACTLOOPSOUND:
tmpx#=entityelement(e).x
tmpy#=entityelement(e).y
tmpz#=entityelement(e).z
if aiactionseq(seq).value=-1
tsnd=entityelement(e).soundset
else
if aiactionseq(seq).value=-2
tsnd=entityelement(e).soundset1
else
tsnd=aiactionseq(seq).value
endif
endif
loopinternal3dsound(tsnd,tmpx#,tmpy#,tmpz#)
entityelement(e).soundlooping=tsnd
endcase
case AIACTSTOPSOUND:
if aiactionseq(seq).value=0
if entityelement(e).soundset>0 then stop sound entityelement(e).soundset
else
if aiactionseq(seq).value=1
if entityelement(e).soundset1>0 then stop sound entityelement(e).soundset1
endif
endif
if entityelement(e).soundlooping>0
if sound exist(entityelement(e).soundlooping)=1
stop sound entityelement(e).soundlooping
endif
entityelement(e).soundlooping=0
endif
endcase
`
case AIACTALTTEXTURE:
if obj>0
if aiactionseq(seq).value=0
texture object obj,0,entityelement(e).eleprof.texdid
else
texture object obj,0,entityelement(e).eleprof.texaltdid
endif
if entityelement(e).eleprof.usingeffect>0
if entityelement(e).eleprof.tex1id<>0 then texture object obj,1,entityelement(e).eleprof.tex1id
if entityelement(e).eleprof.tex2id<>0 then texture object obj,2,entityelement(e).eleprof.tex2id
if entityelement(e).eleprof.tex3id<>0 then texture object obj,3,entityelement(e).eleprof.tex3id
set object effect obj,entityelement(e).eleprof.usingeffect
endif
endif
endcase
`
case AIACTSETALPHAFADE:
entityelement(e).ai.alphafade=aiactionseq(seq).value
entityelement(e).ai.destalphafade=aiactionseq(seq).value
if obj>0
set alpha mapping on obj,entityelement(e).ai.alphafade
endif
endcase
case AIACTINCALPHAFADE:
entityelement(e).ai.destalphafade=aiactionseq(seq).value
endcase
case AIACTDECALPHAFADE:
entityelement(e).ai.destalphafade=aiactionseq(seq).value
endcase
`
case AIACTRUNDECAL:
tdecalmode=aiactionseq(seq).value
if tdecalmode=-1
entityelement(e).decalindex=0
else
entityelement(e).decalindex=1
entityelement(e).decalmode=tdecalmode
if entityelement(e).decalmode=6
if entityelement(e).currentweapon=0
entityelement(e).decalindex=0
endif
endif
endif
endcase
case AIACTSHAPEDECAL:
if obj>0
entityelement(e).decalsizex=object size x(obj)
entityelement(e).decalsizey=object size y(obj)
endif
endcase
`
case AIACTTRIGGERFORCE:
taddforcesphere=aiactionseq(seq).value
endcase
`
case AIACTPLRASS:
gosub _player_resettrail
meridinglift=1+e
endcase
case AIACTPLRNOASS:
meridinglift=0
endcase
`
case AIACTPLRMOVEUP:
memovingy#=(aiactionseq(seq).value/100.0)
endcase
case AIACTPLRMOVEDOWN:
memovingy#=(aiactionseq(seq).value/100.0)*-1
endcase
case AIACTPLRMOVEEAST:
memovingx#=(aiactionseq(seq).value/100.0)
endcase
case AIACTPLRMOVEWEST:
memovingx#=(aiactionseq(seq).value/100.0)*-1
endcase
case AIACTPLRMOVENORTH:
memovingz#=(aiactionseq(seq).value/100.0)
endcase
case AIACTPLRMOVESOUTH:
memovingz#=(aiactionseq(seq).value/100.0)*-1
endcase
case AIACTPLRMOVETO:
foundte=aiactionseq(seq).value
if foundte>0
tranmex#=entityelement(foundte).x
tranmey#=entityelement(foundte).y+55.0
tranmez#=entityelement(foundte).z
tranmeangley#=entityelement(foundte).ry+180
gosub _player_resettrail
meridingtransporter=1
endif
endcase
case AIACTPLRMOVEIFUSED:
tname$=entityelement(e).eleprof.ifused$
gosub _entity_findname
if foundte>0
tranmex#=entityelement(foundte).x
tranmey#=entityelement(foundte).y+55.0
tranmez#=entityelement(foundte).z
tranmeangley#=entityelement(foundte).ry+180
gosub _player_resettrail
meridingtransporter=1
endif
endcase
`
case AIACTACTIVATEIFUSED:
tname$=entityelement(e).eleprof.ifused$
tstate=aiactionseq(seq).value
gosub _entity_activatename
endcase
case AIACTACTIVATEIFUSEDNEAR:
tname$=entityelement(e).eleprof.ifusednear$
tstate=aiactionseq(seq).value
gosub _entity_activatename
endcase
case AIACTACTIVATETARGET:
te=entityelement(e).actualtarget-1
if te>0
entityelement(te).activated=aiactionseq(seq).value
entityelement(te).logiccount=0 : entityelement(te).logiccountburst=5
entityelement(te).dormant=0
endif
endcase
case AIACTACTIVATE:
entityelement(e).activated=aiactionseq(seq).value
entityelement(e).logiccount=0 : entityelement(e).logiccountburst=5
entityelement(e).dormant=0
endcase
case AIACTACTIVATEALLINZONE:
tactivated=aiactionseq(seq).value
condx1=(entityelement(e).x+(entityelement(e).eleprof.trigger.areax1*100))-50.0
condy1=(entityelement(e).y+(entityelement(e).eleprof.trigger.areay1*100))
condz1=(entityelement(e).z-(entityelement(e).eleprof.trigger.areaz1*100))+50.0
condx2=(entityelement(e).x+(entityelement(e).eleprof.trigger.areax2*100))+50.0
condy2=(entityelement(e).y+(entityelement(e).eleprof.trigger.areay2*100))+100.0
condz2=(entityelement(e).z-(entityelement(e).eleprof.trigger.areaz2*100))-50.0
condy1=condy1/100 : condy1=condy1*100
condy2=condy2/100 : condy2=condy2*100
gosub _entity_activateallinzone
endcase
`
case AIACTPLRADDHEALTH:
if aiactionseq(seq).value<0
tdamage=abs(aiactionseq(seq).value)
gosub _player_takedamage
else
player(1).health=player(1).health+aiactionseq(seq).value
if player(1).health>playerstartstrength then player(1).health=playerstartstrength
endif
endcase
`
case AIACTSETTARGET:
if entityelement(e).possibletarget<>0
entityelement(e).actualtarget=entityelement(e).possibletarget
if entityelement(e).actualtarget>1
tte=entityelement(e).actualtarget-1
entityelement(e).actualtargetx=entityelement(tte).x
entityelement(e).actualtargety=entityelement(tte).y
entityelement(e).actualtargetz=entityelement(tte).z
entityelement(e).losttargetcount=0
else
if entityelement(e).actualtarget=-1
entityelement(e).actualtargetx=bcsoundx#
entityelement(e).actualtargety=bcsoundy#
entityelement(e).actualtargetz=bcsoundz#
entityelement(e).losttargetcount=0
else
`
rem target is player
tdx#=(mex#-entityelement(e).x)
tdy#=(mey#-entityelement(e).y)
tdz#=(mez#-entityelement(e).z)
tdd#=sqrt(abs(tdx#*tdx#)+abs(tdz#*tdz#)+abs(tdy#*tdy#))+0.001
tdxh#=(tdx#/tdd#)*10.0
tdzh#=(tdz#/tdd#)*10.0
ttactualtargetx=mex#-tdxh#
ttactualtargety=mey#-phyeyeheight# : rem too much -meheight#
ttactualtargetz=mez#-tdzh#
`
rem only if actual target position has CHANGED!
taquirelinetoplr=0
if int(entityelement(e).actualtargetx/100)<>int(ttactualtargetx/100) or int(entityelement(e).actualtargety/100)<>int(ttactualtargety/100) or int(entityelement(e).actualtargetz/100)<>int(ttactualtargetz/100)
rem new position of target, so check ground before use it
entityelement(e).actualtargetx=ttactualtargetx
entityelement(e).actualtargety=ttactualtargety
entityelement(e).actualtargetz=ttactualtargetz
rem can go straight to players position to start trail if plr level(ish)
traily#=entityelement(e).actualtargety
if entityelement(e).plrtrailindex<>0
rem entity already has trail, but might be 'follow the leader' nonesense
rem if there is good ground between player and entity, give opportunity to skip
tokay=1
tstbx#=entityelement(e).x
tstby#=entityelement(e).y
tstbz#=entityelement(e).z
tdxi#=tdx#/tdd# : tdyi#=tdy#/tdd# : tdzi#=tdz#/tdd#
for tst#=0.0 to tdd# step 90.0
tstx#=tstbx#+(tdxi#*tst#)
tsty#=tstby#+(tdyi#*tst#)
tstz#=tstbz#+(tdzi#*tst#)
inc gameperfentities3
entityelement(e).raycastcount=0
if static raycast(tstx#,tsty#,tstz#,tstx#,tsty#-50,tstz#)=0
tokay=0
endif
next tst#
if tokay=1 then taquirelinetoplr=2
endif
`
else
entityelement(e).losttargetcount=0
endif
if taquirelinetoplr=2
if abs(traily#-entityelement(e).y)<10.0
rem if on same level
taquirelinetoplr=1
endif
if traily#<entityelement(e).y
rem if player lower than entity
taquirelinetoplr=1
endif
endif
if taquirelinetoplr=1
trailx#=mex# : trailz#=mez#
trailaction=1 : gosub _player_leavetrail
entityelement(e).plrtrailindex=playertrailmax-1
if entityelement(e).plrtrailindex<1 then entityelement(e).plrtrailindex=100
endif
`
endif
endif
entityelement(e).possibletarget=0
` interferes with target lock, but character is stuck (like a barrier)
` entityelement(e).losttargetcount=0
endif
endcase
rem and so on
Do you not also agree that this is a bit much? What do you do when you realise the overhead? Restructure all of that?
The real snippet goes for just under 2000 lines; if all are false, ALL cases are compared for all script codes for each entity in the live game and is performed on every frame. Integer or not, I am sure this will eat FPS for breakfast. Note this may not be the case in the latest version.
It is easy to say when it is not your project, but I am sure that this part of the code would run 500% faster with function pointers. Just store a function reference in the state and call the function stored in the command; no need to check each integer one by one until you find the match.