Testing/Debugging Hacks & Tips

Discussions about Coding and Scripting
User avatar
sektor2111
Godlike
Posts: 6403
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Testing/Debugging Hacks & Tips

Post by sektor2111 »

Because I was bumping ball here at debugging problems we can discuss a little about MapVoteLA troubles happening at start in Multi-Servers. A scenario happening to me multiple times is no longer happen. Multi-Server was using a MH2 - game MonsterHunt, a MonsterHunt having game MonsterHunt as well and for sure both of them having maps with prefix MH.
Not a single time MH2 was loading relics set in MH and MH was loading what MH2 has defined due to prefix and game-type confused by MapVote when it doesn't handle travel correctly - actually there is no travel when server starts. When MapVote it's triggering server-travel, it will set all game types correctly even if you have 3 MonsterHunt games running and 2 DM types + CTF and others.
ActorCLP did not help me in this problem too much and looking at logs probably I was not fascinated at all - I repeat I'm talking about multi-servers already done and functional.
In order to prevent Another load of a a new file I'm starting server using default UT-Logo-Map, not dedicated for game-play and running for whatever 4-8 seconds, ending "match" and letting MapVote to do its task correctly. By using this way, mutators for MH will not run randomly under MH2 MH3 and you can use this way regarding to what others are saying. How to trigger this quick travel ? I wrote a server-start actor some time ago which I changed a few times over time and can be changed anytime because it's not a package. Speaking about packages I find completely USELESS loading all ServerPackages in start-map which has only one purpose: Making MapVoteLA any to handle server over Run-Line - and for such a server running XC_Engine, loading all packages for playing a single game/map over there, for me doesn't make any sense. That's why I wrote those things in that Packager and reason for using/having "packager" tool working as Server-Side.
Paranoia (according to some opinions around but which are already set in reality and they are functional for 3-4 years) goes below:

Code: Select all

class TheStart expands Mutator config;

var config float stDelay;
var float fInterval;
var config bool bStartGames;
var String Map;
var bool bFirstTime, bCheckedMap, bProcessing;

event PreBeginPlay()
{
	log (Self$" waiting "$stDelay$" seconds for travel check");
//	SetTimer(stDelay,False);
	if (stDelay == 0)
		stDelay = 3;
	fInterval = stDelay;
}

function Timeing()
{
	local Pawn P;
	local StatLog SL;

	if (!bCheckedmap)
	{
		SL = Spawn(class'StatLog');
		if ( SL != None )
		{
			Map = Caps(SL.GetMapFileName());
			SL.Disable('Timer');
			SL.Destroy();
		}
//		Map=Caps(Level.GetURLMap());
		bCheckedMap = True;
	}
//	if ( Map ~= RefMap)
	if ( Left(Map,11)=="UT-LOGO-MAP")
	{
		if ( DeathMatchPlus(Level.Game) != None )
		{
			DeathMatchPlus(Level.Game).MinPlayers = 2;
			if (!bFirstTime)
			{
				DeathMatchPlus(Level.Game).NetWait=0;
				DeathMatchPlus(Level.Game).RestartWait=0;
				DeathMatchPlus(Level.Game).bRequireReady=False;
				DeathMatchPlus(Level.Game).bNetReady=False;
				DeathMatchPlus(Level.Game).TimeLimit=4;
				DeathMatchPlus(Level.Game).StartMatch();
				DeathMatchPlus(Level.Game).RemainingTime=3;
				log ("Awaiting end"@Self.Name);
			}

			if (!bFirstTime)
			{
				bFirstTime = True;
//				SetTimer(stDelay/2,False);
				fInterval = stDelay/2;
				bProcessing=True;
				GoTo EndChapter;
			}

			foreach AllActors (class 'Pawn', P)
			{
				if ( P.PlayerReplicationInfo != None && P.bIsPlayer )
				{
					P.PlayerReplicationInfo.Score += 2;
					log (P.GetHumanName()@has new score.);
					bProcessing=False;
					break;
				}
			}
		}
	}
	else
	{
		log (Self$" won't react.");
		log (Self$" has detected being loaded"@Map);
		if ( bStartGames )
		{
			if ( DeathMatchPlus(Level.Game) != None )
			{
				DeathMatchPlus(Level.Game).NetWait=0;
				DeathMatchPlus(Level.Game).RestartWait=0;
				DeathMatchPlus(Level.Game).bRequireReady=False;
				DeathMatchPlus(Level.Game).bNetReady=False;
				DeathMatchPlus(Level.Game).StartMatch();
			}
		}
		LifeSpan=1.000000;
		Goto EndChapter;
	}
EndChapter:
}

Auto State STracking
{
Begin:
	Sleep(0.00);
ALooping:
	Sleep(fInterval);
	Timeing();
	if (bProcessing)
		GoTo('ALooping');
End:
}

defaultproperties
{
	stDelay=3.000000
}
To check well - actor's self destruction - That LifeSpan setup. If that is a problem line can be replaced with "stop" instruction which will go at the end of state code, stop being a state code stuff.
In mean-time here might go other toys which others did not do- part of "Testing/Debugging hacks & Tips" chapter.
MrLoathsome
Inhuman
Posts: 958
Joined: Wed Mar 31, 2010 9:02 pm
Personal rank: I am quite rank.
Location: MrLoathsome fell out of the world!

Re: Testing/Debugging Hacks & Tips

Post by MrLoathsome »

Code: Select all

      LifeSpan=1.000000;
      Goto EndChapter;
   }
EndChapter:
}
Why the extra Goto there? Where else is it gonna go?

Re: Return;

I can only think of 2 reasons why you would use this statement in a function.

1. You are returning a value. Function exits.
2. Your function needs to exit "prematurely". Use Return; there rather than a Goto and :End label.

*Edit 2. (Just re-read the whole thread)

If the extra Goto statements are reducing the number of "accessed nones", that just means you have found a way to skip over broken code.

Code: Select all

if (aPawn == None || aPawn.bDeleteMe)
   {
      GoTo NothingToDo;
   }
   else
The Dragon already pointed out that Return will work fine there, but there are almost always more than one way to do something.

You could also write it like this I think:

Code: Select all

if (aPawn != None && !aPawn.bDeleteMe)
   {
      <Do the stuff...>
   }
This does the same I believe, unless I am having a brain-fart or have sipped too many beers this evening.... :ironic2:
And it skips the else and the GoTo Nothing.

That code is happening in a called function where aPawn is the first parameter is it not?

Is it not much more likely to be '!= none' than '== none'?
Should not any 'sanity checks' have been done before that is even called?

The way you have it is the opposite of optimal.

Why exactly is the extra test on bDeleteMe needed at that point? (It would execute even faster without that check...)

Just some observations that may not be correct. I suspect you are over-thinking things.

Work with the engine, not against it.
8)
blarg
User avatar
Feralidragon
Godlike
Posts: 5489
Joined: Wed Feb 27, 2008 6:24 pm
Personal rank: Work In Progress
Location: Liandri

Re: Testing/Debugging Hacks & Tips

Post by Feralidragon »

Higor wrote: One example: 4 large distance view triggers + 100 monsters are more than enough to make UT crawl.
Are you sure you're not missing a zero or so there?
Either way I didn't test that yet, but on first sight the code seems simple enough and doesn't seem to invoke any particularly slow functions, and it's only 400 iterations, which is not a big deal, and I am pretty sure I do a lot more processing in other things without a hitch.
If you said 1000 monsters or 40 distance view triggers that would make sense, even because at that point it's much slower to iterate through the PawnList than it is to use a "Foreach AllActors", due to the sheer speed of the native code at that point.

I will test this out later today to find out if that's indeed the case, and what is causing the slowdown if so. :)
MrLoathsome wrote: If the extra Goto statements are reducing the number of "accessed nones", that just means you have found a way to skip over broken code.
This.
The problem generally lies between the chair and the monitor, as they say.
But some think too highly of themselves and of some specific people to realize this, or they realize it but cannot accept it.
MrLoathsome wrote: You could also write it like this I think:

Code: Select all

if (aPawn != None && !aPawn.bDeleteMe)
   {
      <Do the stuff...>
   }
This does the same I believe, unless I am having a brain-fart or have sipped too many beers this evening.... :ironic2:
And it skips the else and the GoTo Nothing.
Yep, that's exactly right, that's a basic De Morgan's Law.
But I have seen programmers in my own team doing something similar to his (but without the goto), and I smacked them on the head (sort to speak).

Although:

Code: Select all

if (aPawn == None || aPawn.bDeleteMe)
{
    return;
}
has a slightly better performance (I believe, didn't check the bytecode yet), and prevents the entirety of the remaining code of the function to have yet another indentation level and be inside a massive if.
The performance gain is negligible (never micro-optimize, unless within a loop with a lot of iterations or if it runs too frequently), due to the missing "!" which is often overlooked as being an additional operator onto itself, but don't quote me on this yet, I didn't confirm in the resulting bytecode.

And since aPawn is rarely None, both parts of the condition will always be evaluated, as in an OR condition, the second part is skipped if the first is True, while in an AND condition the second part is skipped is the first is False, hence the above never resulting in Acessed None warning.
So even if you invert the condition you gain nothing in terms of logic itself, because the condition is still the "same".
MrLoathsome wrote: Should not any 'sanity checks' have been done before that is even called?
You mean before the function call?
Not exactly, but that's not wrong either.

The function should always validate and sanitize any incoming input, since it's actually one of a function's responsibilities to discern bad input from good input.
This is where things like Exceptions in other languages come from: if a function doesn't like the input, it should reject it by throwing an error or doing nothing, although the former is preferred by default, with the latter being a good option to give (it's what I do in my PHP framework, any specific Exception throwing methods have a "no_throw" optional parameter in case you want it to just return null or false if something is wrong).

However, UScript doesn't have such a thing as Exceptions, although they can be emulated to an extent (with gotos, ironically), and you want to keep the game running, so is just best for most functions to do "nothing" if the input is not valid, and at most throw an warning to the log in the same way that accessing None throws an Accessed None warning instead of crashing the game as it would be supposed to in any other language.

This does not invalidate doing some validation before calling the function, but it should be because in your own code logic it makes sense to be so, and not because you want to protect the function.
If you expect a reference to be always valid, you should just pass it directly to the function without any prior validation and let the function deal with the validation itself, letting you know if something goes wrong.
MrLoathsome wrote: Why exactly is the extra test on bDeleteMe needed at that point? (It would execute even faster without that check...)
While in theory a destroyed actor should yield None, at least in UT99 this is not exactly the case, and there will be many times where a reference towards a destroyed actor will still be different from None, but having bDeleteMe=True.
I caught this case very often, especially in things like homing missiles when a monster would "die" (and hence get destroyed) but the reference would not turn to None so the missile would still follow essentially the monster's soul :lol2: .
So I had to also check for bDeleteMe.
MrLoathsome wrote: I suspect you are over-thinking things.
It goes beyond that I'm afraid, when 2 different people tell him essentially the same thing, just through different words, with him agreeing with the second person, but still contradicting himself immediately after by defending the same practice, meaning that he didn't understand a word of what he's being told, it's just that one of the people is Jesus to him and not the other, so one is always wrong and the other right by default.
Let's see how he reacts with a 3rd person saying almost the same thing but in a different way, probably won't change a thing, unfortunately.
MrLoathsome
Inhuman
Posts: 958
Joined: Wed Mar 31, 2010 9:02 pm
Personal rank: I am quite rank.
Location: MrLoathsome fell out of the world!

Re: Testing/Debugging Hacks & Tips

Post by MrLoathsome »

As always, excellent observations by the Dragon.
Of course those sanity and bDeleteMe checks should still be there. (I may have been over thinking things when posted that 'observation'... :shock: )

I suppose what I was trying to say is that since the calling function has hopefully also done sanity checks, we can attempt to
optimize the function based on the likelihood of aPawn being None.


If you are going to test things at the bytecode level on this, check this. (a 3rd option...)

Code: Select all

if (aPawn == None || aPawn.bDeleteMe)
{
    // Dont GoTo NothingToDo.   Dont do nothing....
}
else
{
    // do all the cool code...
}
That should get the same result, and the code will be 1 return statement smaller. (I think...)
And since it uses the original OR rather than the AND, it should be fastest in real time, assuming that
aPawn is != None most of the time.
blarg
User avatar
Barbie
Godlike
Posts: 2792
Joined: Fri Sep 25, 2015 9:01 pm
Location: moved without proper hashing

Re: Testing/Debugging Hacks & Tips

Post by Barbie »

Higor wrote:

Code: Select all

mov ecx,(caller address)  //MS __thiscall convention puts object address in ECX register
mov eax,[ecx]  //vtable
mov eax,[ecx+vtable_offset] //function pointer
call eax
Something looks wrong here... I guess the third line should be

Code: Select all

mov eax,[eax+vtable_offset] //function pointer
MrLoathsome wrote:

Code: Select all

      LifeSpan=1.000000;
      Goto EndChapter;
   }
EndChapter:
}
Why the extra Goto there?
I use such also as a kind of defensive programming. If I add some code (years) later I don't have to go through the complete existing code but just can add another code block.
MrLoathsome wrote:You could also write it like this I think:

Code: Select all

if (aPawn != None && !aPawn.bDeleteMe)}
True, but I personally avoid the NOT operator because humans have to think one step more than in a not negated condition. So code without negations is easier to read, at least for me.
Feralidragon wrote:Let's see how he reacts with a 3rd person saying almost the same thing
We already had this Dont-Use-Returns discussion if I remember correctly, bot not in this depth. I will not provide arguments here and also not against some other strange thoughts: sometimes it is not possible to argue against belief.
"Multiple exclamation marks," he went on, shaking his head, "are a sure sign of a diseased mind." --Terry Pratchett
MrLoathsome
Inhuman
Posts: 958
Joined: Wed Mar 31, 2010 9:02 pm
Personal rank: I am quite rank.
Location: MrLoathsome fell out of the world!

Re: Testing/Debugging Hacks & Tips

Post by MrLoathsome »

Barbie wrote:I use such also as a kind of defensive programming. If I add some code (years) later I don't have to go through the complete existing code but just can add another code block.
I dunno. If your function is getting large enough that you need to use Goto statements to skip over new stuff, perhaps that function is getting to big.
Barbie wrote:True, but I personally avoid the NOT operator because humans have to think one step more than in a not negated condition. So code without negations is easier to read, at least for me.
Agreed. That's why I put my little disclaimer on there, even though I stared at it for 10 min before I posted it.
I was just searching for more ways to get the same goal accomplished. Just for the current example we are playing with in this thread.

*Edit: Higors code looks right to me....
blarg
User avatar
sektor2111
Godlike
Posts: 6403
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Testing/Debugging Hacks & Tips

Post by sektor2111 »

If we speak about that "goto" from state, let me explain some facts. Code was bigger, else, more complex, at a moment I removed lines (see remarks - code working there and not working in other case) but not taking that in account later - options/changes. Another important random enemy was Power Failures due to weather's habits over here (idiots finally will destroy this planet called Earth - not a coincidence). I went to work after restoring electric power and I forgot all detail due to the time passing and... my state within such occurrences. For THIS reason I was explaining and saying: "Modify" this as you like, don't follow blindly my way, my way isn't the best is just more stable than that trash coding from 2006 and this is a reality.
To reveal some dodging over GoTo you can increase state length and setting up in main class bools (var bool bWhateverSkipStage) for preventing executions, really Return and GoTo can be skipped in more cases. As a matter of fact Testing a bool in my machines always took 0.000000 seconds - if a clocking error can be taken in account, so to speak bool deal is the fastest ever deal for Uscript which I believe is closer to C++ and other programing languages.

Doable stuff here:
Configurable START-MAP not UT-Logo-Map for admins having fancy ideas, writing a smoother and longer state with hard-coded waiting cycles, quitting everything - even the state if Level loaded is not the "Start Server Level", Calling Timer from DeathMatchplus faster and "manually" in order to load bots faster (preventing SetEndCams errors here), modifying some values for people who need other custom options - only by helping admins we help UT, or we can "GoTo Sleep", etc, etc.
User avatar
Barbie
Godlike
Posts: 2792
Joined: Fri Sep 25, 2015 9:01 pm
Location: moved without proper hashing

Re: Testing/Debugging Hacks & Tips

Post by Barbie »

MrLoathsome wrote:If your function is getting large enough that you need to use Goto statements
Oh, we misunderstood us. I didn't target the "goto" but the Why do you explicit end an code block although the parent code block ends immediately after it? It doesn't matter if it is done by "goto", "break", "return" or whatever.
MrLoathsome wrote:even though I stared at it [negated conditions] for 10 min before I posted it
I usually use boolean algebra for that: a + b = ! (! a * ! b)
It also helps me sometimes to create or simplify a composited condition.
Barbie wrote:Higors code looks right to me....
If you look at it again you'll see that the second line could be omitted, because the content of EAX is not used and overwritten in the third line.
sektor2111 wrote:As a matter of fact Testing a bool in my machines always took 0.000000 seconds - if a clocking error can be taken in account, so to speak bool deal is the fastest ever deal for Uscript which I believe is closer to C++ and other programing languages.
All compiler or interpreter I know treat booleans as bytes (1 Byte) or even long (4 Bytes) with just two states. Of course you could handle booleans as real booleans with bit operators on larger variable types (similar to PolyFlags for example) and you win less memory consumption on the price of CPU cycles aka performance.

I guess that if you do your measuring with bytes or integers you won't see a difference to boolean.
"Multiple exclamation marks," he went on, shaking his head, "are a sure sign of a diseased mind." --Terry Pratchett
User avatar
sektor2111
Godlike
Posts: 6403
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Testing/Debugging Hacks & Tips

Post by sektor2111 »

Speaking about "performance" UE1 (which is one of my small hobby things) is not that CPU intensive as others if you follow some rules. A map similar to stock ones runs good even on a lousy machine handling a server and a client in the same time with no problems.
When I sampled some of my toys, those using GOTO and the rest, I see that it's not the best optimization, good. Good or not, I could play games for hours (week-end usually gives me more time), 6 years before almost each time I was witnessing crashes, exactly, not a single crash and not in a single server - evidences are those stupid Revote RestartMap maps and so on - accepting nasty stuff and having some excuses toward "quality" used.
If a code runs slower and more exhaustive it doesn't mean it's that harmful - TESTED, it's only not the best way, it could be better. My problems from nowadays are those 250 calls - probably you know them - which I think are not coming from function as much as the function is called from somewhere because an actor is badly flawed, happening with and without my patch "goto" types and so on. THERE is my focus right now and not accelerating some executions to gain MORE iterations when calls are done multiple times. Let's say that the most of Berserker crashes have gone without blabbering around WhatToDoNext - that was only a half of problem, cycles still running and moving crash into SetEnemy - more rare but still happening.
I added Sleep(0.2) at beginning of state Attacking from ScriptedPawn breaking stupid cycles and giving to pawn a "Reflex". I was playing some of those "HTD" maps (there isn't too much HTD anyway) with a crap-ton of Berserkers nearby primary start Location. Result = 0 Crashes.

Note:
A XC_Server running a heavy loaded map which do requires a lot of patching, will affect XC specific "sending file channel" for new comer in that stage because engine will go busy - here... a slow machine is not the best option but... It Works and this is a good thing.

Edit: Question for Higor
Is it possible to setup some funky UScript Translation of FindPathToward ? It will be very slow but I would like to understand it better, for me UScript it's more visible, and I will know better what to take in account in chapter Pathing.
User avatar
Feralidragon
Godlike
Posts: 5489
Joined: Wed Feb 27, 2008 6:24 pm
Personal rank: Work In Progress
Location: Liandri

Re: Testing/Debugging Hacks & Tips

Post by Feralidragon »

MrLoathsome wrote: I suppose what I was trying to say is that since the calling function has hopefully also done sanity checks, we can attempt to optimize the function based on the likelihood of aPawn being None.
Optimizing a function based on the likelihood of certain values is a good way of doing it, but generally it boils down to which calls and conditions are just faster to do.
For instance, a good rule of thumb is that any check which requires a function call goes last, compared to normal equality or inequality conditions, due to the overhead of a function call, for example.
From there is down to which functions are more costly and put those to last too, if possible.

This is one of the reasons why I asked Higor if it was possible to make a profiling tool, and it seems possible, although probably no one will pick that up anytime soon.
MrLoathsome wrote: If you are going to test things at the bytecode level on this, check this. (a 3rd option...)

Code: Select all

if (aPawn == None || aPawn.bDeleteMe)
{
    // Dont GoTo NothingToDo.   Dont do nothing....
}
else
{
    // do all the cool code...
}
That should get the same result, and the code will be 1 return statement smaller. (I think...)
And since it uses the original OR rather than the AND, it should be fastest in real time, assuming that
aPawn is != None most of the time.
Bytecode-wise, the only difference in adding a return there or not, is an extra instruction, however it only occupies an extra byte in the package, as the number of instructions executed regardless is exactly the same: whether the condition is false or true, they will execute the same return, just from slightly different memory addresses.

So the real question is if you're up to sacrifice code readability for an extra byte in the package?
I am willing to wager that good code with comments takes far more space, especially those with some ASCII art in them. :mrgreen:

Also, as I pointed out earlier, whether you use an OR or an AND, the base condition will be exactly the same, so when it comes to the None equality the second part of the condition (the bDeleteMe) is always executed regardless.
For instance, for aPawn=<a valid pawn, so != None>:
aPawn == None || aPawn.bDeleteMe results in False || ..., so the bDeleteMe part must be checked too;
aPawn != None && !aPawn.bDeleteMe results in True && ..., so the bDeleteMe part must be checked too as well.
No optimization as far as the None is concerned.

The only optimization of the OR in this case, is the removal of a NOT operator which is used in the AND condition as !aPawn.bDeleteMe (and this I just really confirmed, the operator bytecode is really there).


Also:
Higor wrote: One example: 4 large distance view triggers + 100 monsters are more than enough to make UT crawl.
I just tested this, and it was a very interesting experiment I did for the first time, because I realized during my tests that Higor might not have been talking exactly about the PawnList and the triggering, but about the collision radius/height instead, so native iterations and not exactly UScript level iterations.

In my test, I created a reasonably big map (10.000 x 10.000 x 1.000), with 100 pupaes in it, and I added 4 distance view triggers, with a timed trigger to trigger them every second.
At first, I set the collision radius and height to 60.000, and with even just 3 triggers, the map wouldn't even start, the game would complain on the lack of virtual memory.
Then I started to remove pupaes so I had 50, then 25, and so on, and even with just 1 pupae it would crash.

So then I kept the 100 pupaes and the 4 triggers, and reduced the collision to 30.000, and this time the map didn't crash, but was extremely slow.
Then I reduced the collision to 15.000 and it got much faster.

To note: the map actually always started being fast, it would only become slow once the pupaes saw me and wanted to "greet" me, by coming to me.

From here, I noticed that although this type of trigger iterates through the PawnList and checks the collision through UScript, it had bCollideActors=True.
After I set it to False to not longer process collision, it was no longer a problem, and everything was pretty much as fast as if I didn't have anything.

Therefore:
@Higor: did you actually mean a problem with the collision hashing, with actors moving within the collision space and such, or is it something else and I did my test wrong?
User avatar
Barbie
Godlike
Posts: 2792
Joined: Fri Sep 25, 2015 9:01 pm
Location: moved without proper hashing

Re: Testing/Debugging Hacks & Tips

Post by Barbie »

Feralidragon wrote:the map wouldn't even start
This also happens with a map with only 2 Actors: a playerstart and a (normal) trigger with astronomic high values for its collision cylinder (try (999999/999999). I run into this earlier, see post Heavy lags in MH-Nagrath.
"Multiple exclamation marks," he went on, shaking his head, "are a sure sign of a diseased mind." --Terry Pratchett
Higor
Godlike
Posts: 1866
Joined: Sun Mar 04, 2012 6:47 pm

Re: Testing/Debugging Hacks & Tips

Post by Higor »

Sheeeeeeeeeeeeit... typo in ASM

Code: Select all

mov ecx,(caller address)  //MS __thiscall convention puts object address in ECX register
mov eax,[ecx+vtable_pointer_offset]  //vtable (added offset for some platforms that put it at end of struct)
mov eax,[eax+function_offset_in_vtable] //function pointer (fixed typo)
call eax
Feralidragon wrote:@Higor: did you actually mean a problem with the collision hashing, with actors moving within the collision space and such, or is it something else and I did my test wrong?
Remember this map?
viewtopic.php?f=5&t=3847&hilit=holdthel ... =15#p37156

Well, collision hashing could be one of the causes too.
Did you try with XC_Engine + CollisionGrid.dll?
User avatar
sektor2111
Godlike
Posts: 6403
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Testing/Debugging Hacks & Tips

Post by sektor2111 »

If we are still discussing that "aPawn != None", maybe it's time for updates as long as the code is changed now and uses a log. Why log ? Because if Creature won't spawn the master blaster function is just useless called and this will increase LENGTH of code with no purpose if result is NONE.
Regarding to bDeleteMe check - I repeat what problem was solved in my mutator "DynAI" (if I well recall the name). Mutator creates a mobile navigation by attaching Nodes to a base which is tested if is suitable for moving nodes around. In a CTF map sampled by Oijtroc it was badly crashing. Took me a few hours to figure WTF was happening. I've added a bDeleteMe stuff and crash was gone. Actually that thing was probably adjusted being a bDeleteMe actor and Engine was skating there or whatever was doing.
Keeping in mind that spawning might go borked like that I simply added a wrapper as long as I could see that even In Bot code used by Epic themselves so probably this practice will not damage anything because testing a bool do takes less than a blink - getting over their bIsPlayer fast methods for players and what Skaarj does here :loool: - in this case I completed another check (other CPU cost) and preventing this way other funky crap processing of un-existent PlayerReplicationInfo. Here I over-done some fixes but I'm still happy about my controllers:

Code: Select all

function int ReduceDamage(int Damage, name DamageType, pawn injured, pawn instigatedBy) //screwed properly
{
	local bool bKL, bV;
	local Weapon W;

	bKL = ( instigatedBy != None );
	bV = ( injured != None );

	if ( bV )
	{
		if (injured.Region.Zone.bNeutralZone)
			return 0;
		if ( injured.bIsPlayer && injured.PlayerReplicationInfo == None )
			injured.bIsPlayer = False;
	}
	else
		Return 0;
	if ( !bKL )
		return Damage;
	if ( bKL )
		if ( InstigatedBy.bIsplayer && InstigatedBy.PlayerReplicationInfo == None)
			InstigatedBy.bIsPlayer = False;
....
This is one of my "Skaarj and Co" mitigations. Even if you get mad or not I will keep them as they are, as long as in such a controller unkillable Skaarj Bug is a myth.
As for Triggering creatures probably in 2018 I don't need DistanceViewTriggers - I have actors making ScriptedPawn to play DM and methods for annoying monsters are a more than one (state solution is one) and less CPU intensive (except when 600 Creatures will do path Seeking to Victim...). I can setup a sort of MH map where Monsters will not sit down looking at walls, will start hunting players (any) as soon as match is started without any Trigger and anything having a stupid size like that.
User avatar
Feralidragon
Godlike
Posts: 5489
Joined: Wed Feb 27, 2008 6:24 pm
Personal rank: Work In Progress
Location: Liandri

Re: Testing/Debugging Hacks & Tips

Post by Feralidragon »

Higor wrote: Well, collision hashing could be one of the causes too.
Did you try with XC_Engine + CollisionGrid.dll?
Just tried, and it works.
No longer crashes even if I set the collision to stupid values like 100.000, and the performance never takes a hit.
This is a very cool thing you made here. :)
Higor
Godlike
Posts: 1866
Joined: Sun Mar 04, 2012 6:47 pm

Re: Testing/Debugging Hacks & Tips

Post by Higor »

CollisionGrid.dll was designed with 'speed' as the one and only consideration, and it was a very interesting experience I gotta admit.
The code on average takes between 40% and 70% of the original hash's computing time to perform the same tasks, with peaks of 10% when the map has movers overlapping each other (1000% faster).
There's 3 reasons CollisionGrid.dll is so fast:

- Container Logic:
https://github.com/CacoFFF/XC-UT99/blob ... ridTypes.h
https://github.com/CacoFFF/XC-UT99/blob ... rLogic.cpp
1) Global container, these actors are always queried.
2) Grid (block container), a set of (512u)^3 blocks.
3) Octree inside grid element. (tree container, subdivides up to 3 times).
The code isn't simple but it's been perfectly adapted to what a UE1 game is.

The grid will be allocated according to the map's BSP nodes, so small maps will allocate smaller grids.
Small actors that fully fit inside one of the 512x512x512 blocks will be assigned to the individual octree (minitree).
Actors that spill into other blocks will be assigned to the block container.
If an actor fits all of the grid or over 128 blocks, it will be put in the global container.

When an actor is added into a container, an 'ActorInfo' element is attached to the container with some precomputed data in it, the actor's 'CollisionTag' variable will store the memory address of this ActorInfo element for quick access.

- Memory management:
https://github.com/CacoFFF/XC-UT99/blob ... /GridMem.h
The memory 'manager' consists on specialized 'Element Holders' and a 'Generic stack', the element holders in particular are allocated in sizes multiples of 4kb for maximum efficience with the OS's allocator.
The element holders contain a list of indices and a list of elements, the indices indicate which elements are unused and are ready to be delivered to the grid for usage.
When no more elements can be delivered (all in use), the element holder can attach a copy of itself for more memory.
When an element is no longer used and is released, it is optionally deinitialized and simply added to the 'free' index list.

Some of the advantages of this is that the memory is still available after releasing so it won't cause GPF's, requesting and releasing don't even require a OS allocator call, and makes possible verifying that an address/pointer is indeed valid (by checking against the element holders).
All of this becomes possible because elements all have the same size and therefore no additional logic is needed besides picking a result from a list.
The element holders contain ActorInfo and MiniTree elements which are constantly requested and released on every frame.

The generic memory stack is a simplified version of UE's FMemStack, although it has a limit of ~5600 hit results.
So try not to add too many actors to the map.

- Vector Math + specialized cylinder queries:
https://github.com/CacoFFF/XC-UT99/blob ... GridMath.h
https://github.com/CacoFFF/XC-UT99/blob ... ueries.cpp
Built in Visual Studio 2015 and GCC 5.2 (had to be built from scratch, without UT headers), vector intrinsics are fully supported and potentially produce better code that ASM blocks.
For that reason the basic vector type has 4 coordinates instead of 3 (X,Y,Z,W) and is used all over the system, with the exception of when translating data between Game and Grid needs to be done.

The usage of vector math has been finetuned into a more procedural way, ensuring that every block of operations shouldn't use more than 6 registers to avoid any unnecessary stack store+read (keeping the data in SSE registers).
Cylinder query has been split in 3 different functions: vertical (1D), horizontal (2D), generic (3D).
The main reasoning behind this is that PHYS_Walking movement internally does a lot of vertical+horizontal traces for every single movement frame, so this way a lot of unnecessary calculations are avoided when moving in areas full of collidables.
Post Reply