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 »

Feralidragon wrote:could simply be:

Code: Select all

    if (aPawn == None || aPawn.bDeleteMe)
    {
        return;
    }

    //code
NOPE, and Do NOT expect me to "return" anything from now on, unless I do some conforming stunts. I WROTE code using LABELS on purpose and not copied from nowhere. Do they work ? YES ! Does they do Accessed Nones ? NO ! Then ? And where is the bug here or what is so bad ? Light me with some logic... All craps written by me are operational at this moment.
Did I say the these codes are not for reusing ? I have a template from where I load needs modified for any particular situation. THESE are ONE PURPOSE usage codes NOT Mutators. Ah, yes, I could remove some brackets because I don't need them :ironic: .
Maps patched with these modules are working properly, like I said go and see yourself, these debates are pointless if you don't even look at what I mean.
You can toy with "returns" and "objects" as much as you want, I have MY OWN way - proved functional more than 100 times - NO GAME CRASH, and no borks encountered.
Aside, as I could see so far only your way is better even if my way did not damaged anything. Evidences are hundreds of matches which I was playing with no issues.
Assuming you think these are not working I go back to my working table for next modules because I don't need to setup more blabbering with no purpose.
Some of those latest MapVote mutators are done having a lot of labels and... they are working better than LA13 - they are the heart of a server working with no problems. Never mind... see you in autumn, I have work to do here...
But I might have another stuff to present for those interested.
Snippet from a packager also tested and operational:

Code: Select all

final function ScanAndSetGame()
{
	if ( GmType[0] == "" )
		SaveConfig();
	if (Caps(string(Outer.Name)) ~= "UT-LOGO-MAP")
	{
		log ("Packages are skipped for this map...",'ServPackager');
		GoTo SkipSetup;
	}
	i = 0;
	for (i = 0; i < 32; i++)
	{
		if ( string(Level.Game.Class.Name) ~= GmType[i] )
		{
			log ("Found game-type"@GmType[i],'ServPackager');
			SetupPacks(i);
			break;
		}
	}
SkipSetup:
}
I'm assuming this is bad and "is ugly" even if I could see it very operational, maybe I was drunk and I had some mental problem when I saw it working... :sleep:
And here too:

Code: Select all

event PreBeginPlay()
{
	local String Day;
	local int I;
	local string str;

	str = String(Outer.Name);
	if ( str != "MH-LandsOfNapali" )
	{
		bDisabled = True;
		log ("Another map type"@str@has triggered loading patch files but it's not original map...);
		log ("Patch module will self deactivate.");
	}
	if ( bDisabled )
		GoTo Done;
	I = Len(str)-3;
	str = Right(str,I);
	SetPropertyText("LTag",str);
	log(Self.name@Patching Level.,LTag);
	G = Spawn( class'PathsActor' );
	AddMHObjectives();
	InitPriorities();
//	ChangeNastyActors();
	SpawnShoters();
	I = Level.DayOfWeek;
	switch(I)
	{
		case 0: Day = "Sunday";		break;
		case 1: Day = "Monday";		break;
		case 2: Day = "Tuesday";	break;
		case 3: Day = "Wednesday";	break;
		case 4: Day = "Thursday";	break;
		case 5: Day = "Friday";		break;
		case 6: Day = "Saturday";	break;
	}
	log ("Today is "$Day$".",LTag);
	if ( I > 0 && I < 5 )
		log ("Map is running with normal patch",LTag);
	if ( I == 0 || I >= 5 )
	{
		log ("Master Patcher wants to run map as Week-End Version!",LTag);
		log ("Attempt to find Week-End add-on...",LTag);
		AddWeekEndStuff();
	}
	InitialState='PathsMapping';
Done:
	log ("PreBeginPlay Completed...",LTag);
}
And so on...
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 »

Holy shit... that's borderline paranoia right there.

Stuff works... so what? I have 7-year old code running in a few dozens of machines serving millions of users, which still works, but that doesn't mean that the code I wrote then is good code, far from it, it's a nightmare.
It's remarkably easy to make any code work, it's harder to make it good code which you can extend and scale without issues, and which you can easily read later on.

I even understand where you may be coming from with Objects, although I said repeatedly that you don't need to extend from Object, you can do the same things with Actors (although they're objects too of course).

But what could possibly be your reasoning against return statements?
Do you even realize that at the end of a function there's also an implicit return at the byte code level, which is exactly the same as an explicit return anywhere in your function?
Meaning that your "goto" actually adds a useless instruction, wasting another cycle?
How else would the function actually return in the end I wonder?

There are some things about the engine which you have to be wary about, sure, but return statements? Come on now, that's just insane.

Just to show to you how insane that is, I just went the extra mile to show you the exact byte code resulting between different takes on the code:
1)

Code: Select all

function doStuff()
{
    goto some_label;

    some_label:
}
results in:
UScript_Bytecode_Goto.png
UScript_Bytecode_Goto.png (4.98 KiB) Viewed 1845 times
Only the green hexadecimal bits are the relevant ones, that's the resulting function bytecode:
06 03 00: it's the "goto" instruction;
04 0B: it's the return instruction... but there's no return statement in the code above, right? Yep, this is the implicit return every single function has at the end.


2)

Code: Select all

function doStuff()
{
}
results in:
UScript_Bytecode_NoReturn.png
UScript_Bytecode_NoReturn.png (4.84 KiB) Viewed 1845 times
As you can see, no code at all, yet the bytecode residing in it is just a single return (04 0B).


3)

Code: Select all

function doStuff()
{
    return;
}
results in:
UScript_Bytecode_Return.png
UScript_Bytecode_Return.png (4.91 KiB) Viewed 1845 times
Which, as you can clearly see, now has 2 return instructions: the explicit one corresponding to the one I added to the code, plus the implicit one at the end.
And they are both 04 0B, in other words, they are exactly the same instruction.

Furthermore, since it's a return, although 2 instructions are saved in the bytecode, unlike your goto statement, only one of them actually ever runs.
And since the compiler is naive and simply translates the high level code into low level bytecode "as is", it doesn't optimize the explicit return away.

And even so, besides not running the second return, despite being 2 instructions, it uses up 1 less byte in the code, reducing the package size by that value.
It's a laughably small difference, but for a guy like you who gives so much importance to these details, this just proves your insanity around things like your aversion for return statements.

You're not being clever by using goto statements, you're not preventing any problems or optimizing anything, you're just being paranoid.

But don't worry, I won't go the extra mile for you again like this, I know you will just keep spouting half truth and half nonsense.
It's a shame really, considering how passionate and hardworking you are, but clearly discussing anything with you is a complete waste of time, it's far more productive bouncing balls off a wall.
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 »

Feralidragon wrote:It's a shame really, considering how passionate and hardworking you are, but clearly discussing anything with you is a complete waste of time, it's far more productive bouncing balls off a wall.
Then bounce balls of a wall, before bouncing take a look at some code which Higor has wrote if you have a "GoTo" problem:

Code: Select all

event PostLogin( playerpawn NewPlayer )
{
	local Pawn P;
	local array<Texture> TextureList;
	local Texture T[3];
	local int i, j, TLMax;

	// Start player's music.
	NewPlayer.ClientSetMusic( Level.Song, Level.SongSection, Level.CdTrack, MTRAN_Fade );
	
	// replicate skins
	ForEach PawnActors( class'Pawn', P,,, true, NewPlayer.NextPawn) //Guaranteed to not collide with NewPlayer
	{
		if ( P.bIsPlayer )
		{
			if ( P.bIsMultiSkinned )
			{
				For ( j=0 ; j<4 ; j++ )
				{
					if ( P.MultiSkins[j] != None )
					{
						For ( i=0 ; i<TLMax ; i++ )
							if ( P.MultiSkins[j] == TextureList[i] )
								Goto NEXT_SKIN;
					}
					TextureList[TLMax++] = P.MultiSkins[j];
					NEXT_SKIN:
				}
			}
			else if ( (P.Skin != None) && (P.Skin != P.Default.Skin) )
			{
				For ( i=0 ; i<TLMax ; i++ )
					if ( P.MultiSkins[j] == TextureList[i] )
						Goto NEXT_PAWN;
				TextureList[TLMax++] = P.Skin;
				NEXT_PAWN:
			}

			if ( P.PlayerReplicationInfo.bWaitingPlayer && P.IsA('PlayerPawn') )
			{
				if ( NewPlayer.bIsMultiSkinned )
					PlayerPawn(P).ClientReplicateSkins(NewPlayer.MultiSkins[0], NewPlayer.MultiSkins[1], NewPlayer.MultiSkins[2], NewPlayer.MultiSkins[3]);
				else
					PlayerPawn(P).ClientReplicateSkins(NewPlayer.Skin);	
			}
		}
	}
	
	i=0;
	j=0;
	while ( i < (TLMax-3) )
		NewPlayer.ClientReplicateSkins( TextureList[i++], TextureList[i++], TextureList[i++], TextureList[i++]);
	while ( i<TLMax )
		T[j++] = TextureList[i++];
	if ( T[0] != None )
		NewPlayer.ClientReplicateSkins( T[0], T[1], T[2]);
	if ( TLMax > 0 )
		Array_Length_Tex( TextureList, 0);
}
And a bool

Code: Select all

event bool AdminLoginHook( PlayerPawn P)
{
	local int i, iP;
	local info NexgenClient;

	iP = Array_Length_LI( LInfos);
	while ( i<iP )
	{
		if ( LInfos[i].Player == none || LInfos[i].Player.bDeleteMe )
		{
			Array_Remove_LI( LInfos, i);
			iP--;
			continue;
		}
		if ( LInfos[i].Player == P )
		{
			if ( (Level.TimeSeconds < LInfos[i].DenyUntil) || (LInfos[i].BadLoginCount > MaxBadLoginAttempts) ) //This player is spamming the login
			{
				if ( LInfos[i].BadLoginCount++ > MaxBadLoginAttempts && bKickAfterMaxLogin )
				{
					P.ClientMessage("Kicked due to excessive AdminLogin attempts("$MaxBadLoginAttempts$")");
					if ( ViewPort(P.Player) == none ) //Never destroy local player
						P.Destroy();
					Array_Remove_LI( LInfos, i);
					iP--;
					return false;
				}
				LInfos[i].DenyUntil += LoginTryAgainTime * Level.TimeDilation * 0.1;
				return false;
			}
			iP = i;
			Goto POSITIVE_RETURN;
		}
		i++;
	}
	Array_Insert_LI( LInfos, iP);
	LInfos[iP].Player = P;

	POSITIVE_RETURN:
	LInfos[iP].DenyUntil = Level.TimeSeconds + LoginTryAgainTime * Level.TimeDilation;
	if ( bNexgenAdminLogin ) //Must be done here to prevent lag exploits
	{
		NexgenClient = FindNexgenClient( P);
		if ( NexgenClient != none && (InStr(NexGenClient.GetPropertyText("rights"),"L") >= 0) ) //L is server admin
		{
			Log("Administrator logged in using Nexgen (XC_ServerActor): "$P.PlayerReplicationInfo.PlayerName);
			P.bAdmin = True;
			P.PlayerReplicationInfo.bAdmin = P.bAdmin;
			BroadcastMessage( P.PlayerReplicationInfo.PlayerName@became a server administrator. );
			return false; //Handle myself
		}
	}
	return true;
}
I gotta admit that Higor has used it in a different way, but I still do not have any clue what is that bad and where is the tragedy in my code. Good luck with ball, please avoid windows, because I'm not going to change my methods soon, I could gain smaller packages even with those extra bits from time to time by simply removing stupid codes which I could see written in various other paranoia type works. Like here - NOT written BY ME:

Code: Select all

if (bIsPlayer)
     Destroy();
else
     Destroy();
Funky testing here with IF and ELSE it's really useless but these were codes used and I'm not sure if are not still used... And if you want more samples about "mods" x+y times worst than my bits, we can keep going for at least 1 week and 3 months properly defaming those "coders". Shall we ?
As for increasing cycles I have methods to break them, other troubles are haunting me not these.
Feralidragon wrote: But what could possibly be your reasoning against return statements?
Here... I won't talk too much about what I figured somewhere, I keep it for myself. A server has often quitting when was using a mod with a ton of "return" used, names of mods and stuff is not important, quitting problems have gone when I removed those things and server had a sudden "return" to normal... So I have my internal arguments toward my "paranoia".
Edit: Something which I did last time.
In a multi-server I went to setup several things with a delay, after that I could see some mods running different and way better - letting patching packages to run first. But assuming this is another supposed "paranoia" of mine I won't post anything about that, so I can have a joy around multi-mods without having flaws - not even where such server started using a MapVoteLA type might do a soup with mutators, causing ugly stuff to happen at first entrance - not my case.
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 »

In other words, you haven't the slightest clue on why Higor did what he did, so you go ahead and try to copy him expecting your code to be magically better and more stable. Did I understand that right? :lol2:

For a guy always yelling about having facts and not assumptions, you're the one making all the assumptions here, and the wrong ones to boot.
You're not telling me why using return statements is problematic, because you do NOT have the faintest idea of why either, you are just trying to mimic Higor's code and hope that it works better, you're no better than a fanboy.

So let me tell you this: Higor's use of "goto" is actually acceptable-ish in that context, it's pretty much the only case in programming in general where a "goto" is OK-ish, since he's just exiting a nested loop, as it requires a bit more code and complexity to do the same thing without "goto" statements, and that usage still keeps the code structured.
Don't believe me? Here: https://stackoverflow.com/questions/351 ... using-goto

Even so, in that case the goto statements could be avoided entirely and the code could be much cleaner, and the loops and such are not nested enough to warrant the usage of goto statements to begin with, to not mention the code redundancy (which is generally a bad thing) and an O(n²) time complexity, although the latter is not very important since the number of expected cycles is very small and limited to make much of a difference.
In other words, the code you have shown me is not exactly stellar, and I don't really care where it comes from, an argument from authority is a fallacy onto itself.

Furthermore, if you're going to try to mimic Higor, then I will let you know that when Higor first appeared and started to do things for the game several years ago, namely for Siege at UnrealKillers, he started posting snippets of UScript code and such of things he was learning about on the way, and there was a particular snippet where he was first finding about "goto" statements in the context of UnrealScript, and he was on the verge of starting to use them heavily (like you do, but he did it better still).
I put a stop right then and there by actually letting him now about on how bad of a practice it was, and he resisted at first, then I explained my reasoning, and I believe (although I don't remember 100%) I gave him a link that explained it better than me.

From there, he agreed to not use them at all, and all the snippets afterwards were clean and he used the structured way of doing things with for loops and such, and the code was very clean from there on.
This is actually the first time I see him using "goto" statements since then, meaning that he probably ended up using goto statements anyway, keeping a bad programming habit.

Again, there are things you have to be wary about, and Higor went down to the guts of the engine to get some valuable information to us, and to show why some things were the way they were, and I also learned in the process.
Not only him, a but folks like Anthrax which explained to me the nuances of the garbage collection in this engine, knowledge that I also applied in my own stuff (namely NW3 for instance, everything is set as such that all actors can be garbage collected at any given time during the match, and not only on map change).

However, this does not mean that everything they write is gospel, and it doesn't mean that they will produce quality code 100% of the time, they can also produce turds like everyone else, they're as much human as you and me, so you have to have critical thinking here when you check their stuff, and ask yourself and them, why they did something the way they did, instead of assuming that something is wrong with the way you do it and try to mimic theirs like that.
Sometimes they may not have a good reason other than they were probably high or simply made mistakes, or they may have a simpler one that has nothing to do with UScript or the engine specifically, but programming in general.

In this case, it has nothing to do with the engine.
And something like:

Code: Select all

if (bIsPlayer)
     Destroy();
else
     Destroy();
is just stupid.

Am I wrong? Please counter with a fact why so, so we may all learn something new. I do not have big issues in being proven wrong, I have huge issues when I am said that I am wrong and no facts or explanation are given to corroborate it, which is exactly what you're doing.

I went as far as showing the actual bytecode, maybe you could reply in kind? Are you even capable of doing so?

The engine is far more stable and consistent than you make it out to be.
There are things you are right to be wary about, others are really just misled and ignorance-driven paranoia, over the fact that you don't actually try to find out these things.
I doubt you even looked or understood what I posted about the bytecode above, since you're treating things like mojo still.

If we're actually pulling the big guns in the end of all this, I can just pull NW3 and show you that although it has nukes it doesn't actually blow up servers for hours on end, with all sorts of monsters, spam, effects and mayhem going on.
It has bugs, sure, it may have one or another thing which may actually lead to a crash, most of them due to the actual engine, and some flaws in general, especially since it was done years ago when I didn't have all this experience, but if working stuff is your frame of reference, there you go, I don't use "goto" statements there at all (except in state code, it's the 2nd acceptable case), I use a ton of returns and whatnot, thousands upon thousands of lines of code, and not always that great of a code, and look at it, it runs as smooth as butter, and it's a mod that pushes the engine into spawning tons of things, processing lots of things (especially physics-wise with the gore), and so on, you're probably not going to find many things which have so much going on at the same time in a mod.

From there, feel free to reply with something actually productive, or just don't, up to you.
Once again this ended up much bigger than I wanted, but you're really pushing my buttons here, you cannot give a single straight factual answer at all, because in the end you do not have one, it's that simple.
Higor
Godlike
Posts: 1866
Joined: Sun Mar 04, 2012 6:47 pm

Re: Testing/Debugging Hacks & Tips

Post by Higor »

Broke: trying to optimize by outsmarting the compiler.
Woke: using the compiler's smartness to produce optimizations and good looking code.

The coder needs to be as pragmatic as possible.

==============================================
Unfortunately, the unrealscript compiler is not smart so the only possible optimizations rely not on code compilation, but bytecode interpretation.
Which means these are the only possible optimizations, at these DO come at the expense of code readability:
- Reducing amount of bytecode read operations.
- Reducing amount of non-final function calls.

Goto'ing to return not only achieves none of the above, also makes code less readable.
Also, considering that distant memory jumps may trigger a prefetch operation on the CPU's cache.

==============================================
Calling a function with 32 parameters:

UnrealScript zero-inits structs and natively copies them by data when passing them through functions, always a better solution when calling the function more than once.

Code: Select all

struct ValueObject //Welcome to JAVA, that hell where 'out' params do not exist and you need to pass these as arguments
{
    //32 variables here
};

function Test()
{
    local ValueObject VO;
    // Init VO
    DoSomething( VO );
    // VO: edit only values that need to be edited for next call.
    DoSomething( VO );
}

=========================================
This is something that an actual smart compiler can do, which I like about C++

This thing gives you the memory address of a reusable preallocated buffer, useful for quick buffers that need to be passed in return statements without the burden of having to destroy/deallocate them.

Code: Select all

//*****************************************************//
// Circular Buffer
//
// Provides a single data buffer that can be fragmented
// on demand, while trying to preserve previously
// requested data for as long as possible.
//
Data alignment in memory is very important at low level, you want the CPU working as smoothly as possible.
SSE vector math may crash if you're using instructions that require data to be aligned.
Multi-thread locks need data to be aligned too.

Code: Select all

//Align options
enum EAlignOptions
{
	EALIGN_1  = EALIGN_Byte,
	EALIGN_2  = EALIGN_Word,
	EALIGN_4  = EALIGN_DWord,
	EALIGN_8  = EALIGN_QWord,
	EALIGN_16 = EALIGN_XMMWord,
	EALIGN_32 = EALIGN_AVXWord,
	EALIGN_PLATFORM_PTR = sizeof(void*), //4 in 32-bit, 8 in 64-bit
};
Look at the constant expression conditional (Align > 1)
The compiler is smart enough to create a version of the function without that unnecessary block when called with Align=1
And smart enough to create another version of said function when Align greater than one.

Code: Select all

template<uint32 BufferSize> class TCircularBuffer
{
public:
	volatile int32 Lock;
	uint32 CurPos;
	uint8 Data[BufferSize];

	TCircularBuffer()
		: Lock(0), CurPos(0) {}

	template<enum EAlignOptions Align=EALIGN_PLATFORM_PTR> uint8* Request( uint32 Amount)
	{
		if ( Amount > BufferSize )
			throw "TCircularBuffer cannot be requested more than its Size";

		CSpinLock SL(&Lock); //Only one CPU at a time from here on
		uint32 Start = CurPos;
		if ( Align > 1 ) //Constant expression evaluated by compiler, meaning block will disappear if condition not met
		{
			const uint32 Mask = Align-1;
			Start = (Start + Mask) & (~Mask);
		}

		if ( Start + Amount >= BufferSize )
			Start = 0;

		CurPos = Start+Amount;
		return Data + Start;
	}
};
Last edited by Higor on Thu Jul 26, 2018 1:20 am, edited 1 time in total.
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 »

Now These Are USEFUL explanations. I'm going to copy them.
Aaand...
I think I have another way then, still without "return" - yeah, it's possible and without GoTo as well if you don't like it, but that's for another time...

At the moment I'll configure a sort of function "extended" a bit (perhaps spoofing 20+ arguments) and... I think I'm going to clock execution time on my slowest machine possible, it would be nice to find better solutions for my laziness toward writing long pages.
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 »

Re: Return;

The bytecode answers that pretty much. I never noticed any difference when I tested doing that
in various ways.

Re: Goto statements.

I think I only used a goto in 1 of my uscript projects*, and I probably could/should have done it differently.
(I just wanted to use a goto and a label that day. I used to do a lot of BASIC LONG ago... :roll: )

*Exception for the no-goto rule in uscript I have done would be in state code. Maybe I was doing that all wrong... :confused2:
Last edited by MrLoathsome on Tue Jul 17, 2018 10:32 pm, edited 1 time in total.
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 »

In mean-time. Current PIV HT machine has set and spawned 18 RockTentacles VIA patch file

Code: Select all

ScriptLog: Spawning New Monsters took: 0.002247 seconds.
Should I be worried here ?

Other 74 Monsters with additional properties set and logs included:

Code: Select all

ScriptLog: Spawning New Monsters took: 0.013281 seconds.
Last edited by sektor2111 on Tue Jul 17, 2018 10:40 pm, edited 1 time in total.
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 »

Only 18?
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 »

18 powered up to 1000-2000 HP (random) and other random type of heavy projectiles.
When they die spit some "jelly" spread in pieces. Each piece will bring other creatures from another random internal list...
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 »

sektor2111 wrote:Now These Are USEFUL explanations. I'm going to copy them.
Aaand...
I think I have another way then, still without "return" - yeah, it's possible and without GoTo as well if you don't like it, but that's for another time...

At the moment I'll configure a sort of function "extended" a bit (perhaps spoofing 20+ arguments) and... I think I'm going to clock execution time on my slowest machine possible, it would be nice to find better solutions for my laziness toward writing long pages.
Amazing... :wth:
I'm completely done with you.

Moving on:
Higor wrote: - Reducing amount of non-final function calls.
Is it like virtual functions vs non-virtual?
Higor wrote: Calling a function with 32 parameters:

UnrealScript zero-inits structs and natively copies them by data when passing them through functions, always a better solution when calling the function more than once.

Code: Select all

struct ValueObject //Welcome to JAVA, that hell where 'out' params do not exist and you need to pass these as arguments
{
    //32 variables here
};

function Test()
{
    local ValueObject VO;
    // Init VO
    DoSomething( VO );
    // VO: edit only values that need to be edited for next call.
    DoSomething( VO );
}
That also makes all those "parameters" to be optional.
It's a good way to create methods with an "options" parameter which is actually a struct, and for which you only have to set up the properties that you want.
Unfortunately in the case of UScript, it also means creating a struct specifically for a single method, unless the struct is reusable by design for a more global context in the class.

I have in fact something very similar for the exact same purpose in my PHP framework: it's literally called "Options" (the class), but all the entries are actually lazy-loaded (although in most cases all the entries are read by the method anyway so they end up all loaded up).
It allows an associative array to be given instead of the class instance itself, which I then coerce into the instance, with every property validated and sanitized.

It's very useful especially for utility functions which may take a fair number of options, but also for more widespread things such as "text options" for example (a specific Options subclass I have to define things like the scope and language of the text to be returned), and the best thing is that it can be extended with more properties later on without changing the methods which already use it, although in case of UScript it would require a recompile of course.
Higor
Godlike
Posts: 1866
Joined: Sun Mar 04, 2012 6:47 pm

Re: Testing/Debugging Hacks & Tips

Post by Higor »

With vtables, calling a virtual function takes no more than something like.

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
Where in unrealscript, it takes

Code: Select all

process EX_VirtualFunction opcode + process name parameter (name of function)
iterate on caller/context class (and superclasses!) every field (properties, states, functions) to find matching function.
call function if found, abort game if not found
And unrealscript final function instead of parsing the name, it parses a memory address directly so the iteration is skipped.
If you're making tons of small utilitary/static functions you don't intend to override, it's best to have them compiled as 'final'
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 »

Yes, I read about using "final" before, and because these files are not for future "expanding" I'm using them as final ones. Yesterday I did some checks, except hacking those 1716 NavigationPoints which was taking ages (in my machine not the rented server) the rest of common things for both client/server was working smoother as oil. Okay, I wrote logs and I had to adjust some spawns because a few Pupae pawns were colliding around and did not spawn but the rest do looks properly. I'll use logging in order to no have calls for spawning Nones.

After finishing my template for lazy coders (ones like me) I will try to figure a solution for that HurtRadius but a very Net compatible one, at this moment I don't have any route for that... here I'm thinking about compatibility with stock not only for XC servers... :|
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: And unrealscript final function instead of parsing the name, it parses a memory address directly so the iteration is skipped.
If you're making tons of small utilitary/static functions you don't intend to override, it's best to have them compiled as 'final'
Although in practice, probably around 90% of the calls are non-final ones in the overall game already, so using "final" for the sake of performance doesn't sound like it will improve things nearly significantly enough to make any dent to the overall game performance.
Therefore it seems to me that is always preferable to use "final" whenever it actually makes sense, as in actually using it when you really mean "final", and not for optimization purposes, such as: utility functions (which you already mentioned), setters and getters for private properties, etc.

Having that said, I am not sure if you're aware about XDebug and CacheGrind for example, but I wonder how hard it would be to hook and intersect every UScript function call and in the end output a file like that to be checked so we can actually see which functions are taking the longest and which are being called more often.
It would give a good insight concerning the vanilla game, and when developing mods and checking their impact, to actually know what to optimize afterwards, rather than before.

After all, premature optimization is the root of all evil.
Higor
Godlike
Posts: 1866
Joined: Sun Mar 04, 2012 6:47 pm

Re: Testing/Debugging Hacks & Tips

Post by Higor »

Feralidragon wrote: but I wonder how hard it would be to hook and intersect every UScript function call
You only need to hook the opcode processors, that's something around 2-3 functions.
And yes, believe it or not, I could have sworn seeing a cycle-counter in one of the Unreal Engine builds (probably was UE3) attached to the unrealscript opcode processors, so they did think of that too.

In the end the one thing someone doing unrealscript should worry about are iterators, and the functions being called from there.
That's where the real slowdowns start occuring.
One example: 4 large distance view triggers + 100 monsters are more than enough to make UT crawl.
Post Reply