Testing/Debugging Hacks & Tips

Discussions about Coding and Scripting
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!

Testing/Debugging Hacks & Tips

Post by MrLoathsome »

Thought we should have a thread where we can share any tricks or techniques we use in testing and development.

Here is one to get things started:

Suppose you want to keep track of a few variables in a Tick function.
Using Log statements or BroadcastMessage just sucks every way possible in this case.

This is the quickest way I know of to dump a few variables onto the HUD so you can monitor them
in real time:

Code: Select all

ThePlayer.PlayerReplicationInfo.PlayerName = String(ThePlayer.bFire)@String(FireCnt)@String(NumFires)@String(Tcount);
In this case, the Tick is happening in an Actor that "Owns" ThePlayer. (ThePlayer is a PlayerPawn defined in that class.)
I am monitoring the status of the Primary Fire button, and 3 integer variables internal to the Tick function.
You will probably have to change the line a bit to access your current PRI.PlayerName if you are testing things in a class that isn't operating on playerpawns.
Since it is using PRI, it does not clobber your PlayerName in the User.ini file or anything stupid like that.
Just clobbers it on the scoreboard in the current match. Only a problem if you have trouble remembering your own name.... :loool:
Press F1 and watch the values in real time while you test.

In the event that this is old news, ignore it and post a stale tip of your own.
blarg
User avatar
sektor2111
Godlike
Posts: 6435
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Post by sektor2111 »

Once I was testing if that bDeleteMe is heavy to catch - I read some stuff saying about whatever... Not really hard. Actor in cause might use:

Code: Select all

Destroy();
log (Self@"has bDeleteMe ="@bDeleteMe);
Else several operations done in this state might return errors since this thing is like None. I used a nasty wrapper for DynamicAI mutator because it was badly failing and I couldn't figure what's wrong - stuff was simple destroyed in some map almost instantly after spawn and then any try to move or deal with such a thing was failing into a crash. Similar things occurs in GuidedWarshell at PostRender - post destruction actor still keeps rendering and any code related to self will refer to a NONE thing because some function keeps running even if actor is about to be removed. That one was pretty challenging for me in that time trying to figure what the heck was wrong there...
User avatar
Barbie
Godlike
Posts: 2911
Joined: Fri Sep 25, 2015 9:01 pm
Location: moved without proper hashing

Re: Testing/Debugging Hacks & Tips

Post by Barbie »

I use some extended functions close to stock functions to verify settings. All functions issue a warning if something fails:

Code: Select all

static function bool PropertyHasValue(Actor A, coerce string PropertyName, coerce string ExpectedValue) {
/******************************************************************************
Returns TRUE if the property *PropertyName* of Actor *A* has *ExpectedValue*
******************************************************************************/
	if (A.GetPropertyText(PropertyName) != ExpectedValue)
	{
		warn(A $ "." $ PropertyName @ "should be" @ ExpectedValue $ ", but is" @ A.GetPropertyText(PropertyName));
		return false;
	}
	return true;
}

Code: Select all

static function bool SetPropertyTextEx(Object obj, coerce string PropName, coerce string PropValue) {
/*******************************************************************************
Does the same as *SetPropertyText* but logs a warning if property could not be
set.
Returns TRUE if value was set successfully.
*******************************************************************************/
	Obj.SetPropertyText(PropName, PropValue);
	if (Obj.GetPropertyText(PropName) != PropValue)
	{
		warn(obj $ ".SetPropertyText(" $ PropName $ "=" $ PropValue $ ") faild for" @ obj $", current property value=\"" $ Obj.GetPropertyText(PropName) $ "\"");
		return false;
	}
	else
		return true;
}

Code: Select all

static function bool SetCollisionEx(Actor A, optional bool NewColActors, optional bool NewBlockActors, optional bool NewBlockPlayers) {
/******************************************************************************
Just an encapsulation for *SetCollision* with additional warning on failure.
******************************************************************************/
	A.SetCollision(NewColActors, NewBlockActors, NewBlockPlayers);

	if ( ! class'UtilsSB'.static.PropertyHasValue(A, "bCollideActors", NewColActors))
		return false;
	if ( ! class'UtilsSB'.static.PropertyHasValue(A, "bBlockActors", NewBlockActors))
		return false;
	if ( ! class'UtilsSB'.static.PropertyHasValue(A, "bBlockPlayers", NewBlockPlayers))
		return false;

	return true;
}
"If Origin not in center it be not in center." --Buggie
User avatar
sektor2111
Godlike
Posts: 6435
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Testing/Debugging Hacks & Tips

Post by sektor2111 »

I'm not sure if SetCollision did ever fail. Really "uber" checks are not that useful.
User avatar
sektor2111
Godlike
Posts: 6435
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Testing/Debugging Hacks & Tips

Post by sektor2111 »

Bump based on a recent discussion.
Problem: Unhealthy codes but which doesn't show Accessed None - in exchange result might be crapped up with no single issue shown in logs.
My case (solved now) Replacement using another function a bit wrapped but without failure reporting:

Code: Select all

	aClass = class<Actor>(DynamicLoadObject(aClassName, class'Class', True)); //Not bad, but not that good
	if ( aClass != None ) //If we have class loaded spawn it
	{
		aClass.Default.bCollideWhenPlacing = False;
		A = Spawn(aClass,Other.Owner,Other.tag,Other.Location+vect(0,0,1),Other.Rotation);
	}
//Main Question: What if we don't have the class ? I really forgot it in other location.
//After some electricity failure I have forgot it completely... grrr.
	if ( Other.IsA('Inventory') && A != None )
	{
It's all about a class loaded but its nothing mentioned for the case related to a failure. The only "visible" problem comes in a map where old stuff is being "replaced" with a NOTHING.
Yeah, I'm using quotes for replacing because... I don't destroy old stuff any more (return False) - I might be need it "someday" so it will be only deactivated and new thing spawned - ONLY AT STARTUP. Why destruction ? Does it help ? I think not, and even if I might be wrong or not, I'm using this way for a few weeks. Probably... a public XC_MonsterHunt will be dropped out with a bit of tuning using this way for Level Replacements and Not Stuff owned/not owned by Skaarj. Skaarj are... nice, no one is moving if game is not started. Their weapons are showing up only if a player wants to play else spawning items doesn't make sense.
User avatar
sektor2111
Godlike
Posts: 6435
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Testing/Debugging Hacks & Tips

Post by sektor2111 »

Bumping with something. Higor was adding a native function in XC_Core "StringToName" that can be used with XCGE. However I've done some mapping script which does this "StringToName" without XCGE. Map name signature is a string but it will log it as name - both of them are just... TEXT.

Code: Select all

class EvSeter expands Actor;

var bool bDisabled;
var Decoration Items[16];
var() Name RandActEvent;
var() Name DecosTag;
var string MyMap;
...

final function DoEventSetup()
{
	local Int i, j, k;
	local Decoration D;
	local bool bTooMany;

	MyMap = GetURLMap();
	SetPropertyText("Tag",GetPropertyText("MyMap"));
...
Auto State SettingEvent
{
Begin:
	Sleep(0.5);
Acting:
	if ( !bDisabled )
		DoEventSetup();
End:
	LifeSpan=0.5;
}

Note: GetURLMap() must be called very late (auto state right here) because it works only after PostBeginPlay(). As you can see patience is a good way. Your mod logging can write logs LATER even if tweaks/needs have been done in PrebeginPlay(). In this way you can get whatever TAG(name) you want from a STRING function.
User avatar
sektor2111
Godlike
Posts: 6435
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Testing/Debugging Hacks & Tips

Post by sektor2111 »

Bump - Purpose removing laziness - making computer to write parts of scripts for us.

Ahah, if I'm writing a patcher where I have to address 50 bad InventorySpot Actors, writing a new radius iterator and location around for each of them etc,... perhaps I will have a school feeling pretty disturbing. I load map and I'm making a Temp Copy. I put things needed inside and then I put a builder to write my script according to selected stuff. Select Actor - Push Button - Select - Push. During this time a half of script is generated and later I can write small stuff and... Copy-Paste in similar cases.
[attachment=0]Sc_Helper.PNG[/attachment]

Any other recommendations ?
You do not have the required permissions to view the files attached to this post.
User avatar
sektor2111
Godlike
Posts: 6435
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Testing/Debugging Hacks & Tips

Post by sektor2111 »

Bump >> I don't know if I've been failing chapter "functions declarations" from whatever Wiki page (written ?)
Something like
function DoSomething(int Num, float AFL, Name ident,....)
It looks like I have reached at boundary in one of my parties - Compiler said "Too Many Arguments" (not parameters as described at Wiki).
The super duper Function which I had to CUT was like this:

Code: Select all

final function SetNewMonster( Pawn aPawn, optional int HP,
	optional float DS,
		optional bool bRandProj,
			optional name MstEvent,
				optional string AMenu,
					optional Texture aSkin,
						optional float ZPivotAdj,
							optional Texture MultiSkin0,
							optional Texture MultiSkin1,
							optional Texture MultiSkin2,
							optional Texture MultiSkin3,
							optional Texture MultiSkin4,
							optional Texture MultiSkin5,
							optional Texture MultiSkin6,
							optional Texture MultiSkin7 )
//							optional name Anim )
I have removed last desired Argument, it was argument no. 17. To summarize, for compiler used by UE1, complex function definition must have maximum 16 arguments else it won't compile... I was thinking to write 32 Arguments... I was inspired to test things first before wasting time writing pages of codes which won't compile today and nor in the days after today...
User avatar
Feralidragon
Godlike
Posts: 5498
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:... I was thinking to write 32 Arguments...
Just... why? :omfg:

Not that I haven't written a few lengthy function declarations in the past, but that was only when I didn't know any better...
Nowadays if a function has anything over 6 arguments, I personally already consider to be a lot. But 32? Damn.

What exactly are you trying to do? There's probably a better way to do that.
User avatar
sektor2111
Godlike
Posts: 6435
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Testing/Debugging Hacks & Tips

Post by sektor2111 »

Feralidragon wrote:Just... why? :omfg:

Not that I haven't written a few lengthy function declarations in the past, but that was only when I didn't know any better...
Nowadays if a function has anything over 6 arguments, I personally already consider to be a lot. But 32? Damn.
Cough... Here was about setting up some... added stuff addressing a random particular map problem (or just simple stuff added).
As direct application if map is poorly loaded in some spot, I might spawn things/creatures/items in patch modules mapped as packages and loaded in run-time. These being actors, I might want to setup whatever properties immediately after spawn - as in above case, I set Skin(s), some event, whatever name held by Pawn, even an animation used, not "Fighter" crap. I can keep going with various things (SkaarjTrooper stuff, movement properties, net things without writing a new Pawn for a few properties) just using default Pawns. Even default crapped Pupae might go better with another Pivot and a fresh carcass if goes skinned like a rainbow, orders, alarms, etc, etc.
When I have a function set I can duplicate line changing only location vector without copying blocks and writing long codes for each creature added. But... I think I can combine 2 functions for other completions preventing X pages of scripts written, so I can complete the rest of needs... because I'm not a fan of long scripts writing codes for ages. Probably even 3 functions will help, I'll think about...
This week-end I was a bit out and I did not have a bunch of free time but I'll do some stuff next days if free time won't be eaten by other things (stupid IoT-s devices are triggering me...).
Here I'm sampling incomplete stuff because of this limitation...

Code: Select all

final function SetNewMonster( Pawn aPawn, optional int HP,
	optional float DS,
		optional bool bRandProj,
			optional name MstEvent,
				optional string AMenu,
					optional Texture aSkin,
						optional float ZPivotAdj,
							optional Texture MultiSkin0,
							optional Texture MultiSkin1,
							optional Texture MultiSkin2,
							optional Texture MultiSkin3,
							optional Texture MultiSkin4,
							optional Texture MultiSkin5,
							optional Texture MultiSkin6,
							optional Texture MultiSkin7 )
//							optional name Anim )
{
	local int I;
	local class<Projectile> ProjType;

	if (aPawn == None || aPawn.bDeleteMe)
	{
		GoTo NothingToDo;
	}
	else
	{
		if ( HP != 0 )
			aPawn.Health = int(RandRange(aPawn.class.Default.Health*2+(HP/2),
				aPawn.class.Default.Health*2+HP));
		if (bRandProj)
		if ( ScriptedPawn(aPawn) != None && ScriptedPawn(aPawn).default.RangedProjectile != None )
		{
			I = Rand(5);
			if ( I > 4 ) I = 4;
			switch(I)
			{
				case 0: ProjType = Class'RocketMk2';
					break;
				case 1: ProjType = Class'CannonShot';
					break;
				case 2: ProjType = Class'Razor2Alt';
					break;
				case 3: ProjType = Class'UT_Grenade';
					break;
				case 4: ProjType = Class'flakslug';
					break;
			}
			ScriptedPawn(aPawn).RangedProjectile = ProjType;
		}
		if ( DS != 0 )
		{
			aPawn.DrawScale = DS;
			aPawn.Health = aPawn.Health * aPawn.DrawScale/aPawn.Default.DrawScale;
			aPawn.SetCollisionSize(aPawn.CollisionRadius*aPawn.DrawScale/aPawn.Default.DrawScale
				,aPawn.CollisionHeight*aPawn.DrawScale/aPawn.Default.DrawScale);
		}
		if ( MstEvent != '')
		{
			aPawn.Event = MstEvent;
			log("WK Mechanic has set as Event for"@aPawn.Name@aPawn.Event);
		}
		if ( AMenu != "")
		{
			aPawn.MenuName = AMenu;
			if ( ( Caps(Left((aPawn.MenuName),1)) == "A"
			|| Caps(Left((aPawn.MenuName),1)) == "E"
			|| Caps(Left((aPawn.MenuName),1)) == "I"
			|| Caps(Left((aPawn.MenuName),1)) == "O"
			|| Caps(Left((aPawn.MenuName),1)) == "U"
			|| Caps(Left((aPawn.MenuName),1)) == "Y")
			&& (Caps(Left((aPawn.NameArticle),3)) == " A "
			|| Caps(Left((aPawn.NameArticle),2)) == "A " 
			|| Caps(Left((aPawn.NameArticle),2)) == " A"
			|| Caps(Left((aPawn.NameArticle),1)) == "A") )
				aPawn.NameArticle = " an ";
			log("WK Mechanic has set as Name for"@aPawn.Name@aPawn.MenuName);
		}
		if ( ZPivotAdj != 0 )
			aPawn.PrePivot.Z = ZPivotAdj;
		if ( aSkin != None )
			aPawn.Skin = aSkin;
		if ( MultiSkin0 != None )
			aPawn.Multiskins[0] = MultiSkin0;
		if ( MultiSkin1 != None )
			aPawn.Multiskins[1] = MultiSkin1;
		if ( MultiSkin2 != None )
			aPawn.Multiskins[2] = MultiSkin3;
		if ( MultiSkin3 != None )
			aPawn.Multiskins[3] = MultiSkin3;
		if ( MultiSkin4 != None )
			aPawn.Multiskins[4] = MultiSkin4;
		if ( MultiSkin5 != None )
			aPawn.Multiskins[5] = MultiSkin5;
		if ( MultiSkin6 != None )
			aPawn.Multiskins[6] = MultiSkin6;
		if ( MultiSkin7 != None )
			aPawn.Multiskins[7] = MultiSkin7;
/*
		if ( Anim != '' )
			aPawn.AnimSequence = Anim;
*/
	}
NothingToDo:
}
As you can see I don't have any new Carcass, no animation for start, no alarm, no orders, no weaponry for Trooper types, no better Jump, so this is not that entertaining as I was expecting, but it will be done in other way.
And let's see a call (or multiple calls)

Code: Select all

	NewRot.Yaw=-15480;
	SetNewMonster(Spawn(class'SkaarjAssassin',,,vect(1202,12560,-3089),NewRot),1600,RandRange(0.7,0.98),True,,"BloodyWarrior",Texture'Skaarjw5_1');
	SetNewMonster(Spawn(class'SkaarjAssassin',,,vect(1306,12616,-3089),NewRot),1900,RandRange(0.7,0.98),True,,"InfectedWarrior",Texture'Skaarjw5_2');
In just 3 lines I did some changes at 2 monsters.
User avatar
Feralidragon
Godlike
Posts: 5498
Joined: Wed Feb 27, 2008 6:24 pm
Personal rank: Work In Progress
Location: Liandri

Re: Testing/Debugging Hacks & Tips

Post by Feralidragon »

Having a ton of parameters in a function declaration only because you want to have declarations in a single line upon the spawn of an actor... is a code smell.

That introduces a lot of problems, such as a very long line length, unless you split the arguments into new lines, defeating most of the purpose then anyway, as well having to fill up or skip arguments that you don't want, making it much harder to read and modify.
Plus, if all of a sudden you need more properties, would you just add more parameters on top of the existing ones? Not the best idea, not at all.

Instead, you should have something akin to a factory or an object specifically to set up pawns, and from it you could have functions set in such a way that you could make chains of calls or have everything in the same line or in different lines as desired, and whenever you needed more properties, you could add them to your heart's content.
For example, something like:

Code: Select all

class PawnSetter extends Object
{
	var private Pawn currentPawn;
	
	function PawnSetter start(Pawn p)
	{
		spawnedPawn = p;
		return self;
	}
	
	function PawnSetter setMultiSkin(byte slot, Texture texture)
	{
		if (spawnedPawn != none) {
			spawnedPawn.MultiSkins[slot] = texture;
		}
		return self;
	}
	
	function PawnSetter setAnimSequence(name animSequence)
	{
		if (spawnedPawn != none) {
			spawnedPawn.AnimSequence = animSequence;
		}
		return self;
	}
	
	function Pawn finish()
	{
		local Pawn p;
		
		p = currentPawn;
		currentPawn = none;
		return p;
	}
}
which then you could use as:

Code: Select all

ps = new(None) class'PawnSetter';
myPawn = ps.start(spawn(class'Gasbag')).setMultiSkin(1, texture'customSkin').setAnimSequence('dancing').finish();
or

Code: Select all

ps = new(None) class'PawnSetter';
myPawn = ps.start(spawn(class'Gasbag'))
	.setMultiSkin(1, texture'customSkin')
	.setAnimSequence('dancing')
	.finish();
Disclaimer: the above code is untested and done after midnight, so it may have syntax errors and you could as well extend from Actor instead of Object.
The point above is figurative, there are multiple ways of doing something akin to this.
User avatar
sektor2111
Godlike
Posts: 6435
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Testing/Debugging Hacks & Tips

Post by sektor2111 »

Purpose of these it's unique, adds which I intend to do are addressing other various environments. Once tested and operational, changes are not expected soon - these codes have as target MAPS, are not mutators, maps are not changing themselves so it's pointless to make these very flexible since there is not purpose for than, and then, I have templates with stuff like that. If in spot there are only 4 creatures and by chance I want 2 more next month, I'm only duplicating 2x a line and setting up Location compile-save done, I don't need large blocks copied increasing package's length because I'm not stripping code. Such future small modifications are doable without mismatch errors because I do them compatible each-other, this way for me it's really simple. Last time I wrote patch files for some DM map in 2 or 3 hours, it takes more time testing and operating changes when things are not 100% operational as targeted, however, here I have full control over paths not what Editor does creating dumb links or not creating desired links. Eh, it's just a way of doing not a "How To" for everyone - an option so to speak.

Other note: If you are dropping a pawn in game and client doesn't know where did it came from it's causing a lag (there is a large discussion here because I have facts not assumptions), excuse me but for net play it's not my way creating objects not replicated. If this is actor using simulated functions and sent as package and spawned at begining, things are more smoother for client than a sudden appearances triggering client to render news and lagging. Cacher is one of things done for preventing such net borks by loading everything when game starts and lagging the start not the later game. I've done already a couple of such packages tested at 180-280 ms ping and I cannot complain at all.
Client know function from package, is expecting this new stuff deployed - just need to know when will happen, if something does a spawn only from server client is bugged in first monster appearance's moment, being surprised about an actor which is not mapped. I would not post these if they have not been tested yet.
User avatar
Feralidragon
Godlike
Posts: 5498
Joined: Wed Feb 27, 2008 6:24 pm
Personal rank: Work In Progress
Location: Liandri

Re: Testing/Debugging Hacks & Tips

Post by Feralidragon »

You're headed towards the right direction, but you keep going with squared wheels instead of round ones.
But to each one their own.
User avatar
sektor2111
Godlike
Posts: 6435
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Testing/Debugging Hacks & Tips

Post by sektor2111 »

Feralidragon wrote:You're headed towards the right direction, but you keep going with squared wheels instead of round ones.
But to each one their own.
Of course, you can even check what I said in that server described in that Queen Teleporting related thread (it's a multi-server, btw), perhaps you'll figure some square wheels working better than other rounded ones :wink:. All being said, next posting around testing/debugging subject won't take place because here the only good way is Object in an engine having some flawed crap called Garbage Collector which is forgetting to collect garbage properly from time to time. I'll use objects when I'll write apps in GoLang not in UScript. That one has a superior garbage collector as described by users-programmers, even Micro$oft uses that programming language. The rest is UE1 which needs rewritten and after that maybe I'll fall in love with Objects.
User avatar
Feralidragon
Godlike
Posts: 5498
Joined: Wed Feb 27, 2008 6:24 pm
Personal rank: Work In Progress
Location: Liandri

Re: Testing/Debugging Hacks & Tips

Post by Feralidragon »

You missed the point, completely.

What I have shown could be used with an Actor in the exact same way (I even said so in my disclaimer), and when I mentioned "squared wheels", I meant that you want to do good patching and good code, but the code you're writing does not have almost any quality to it at all, it looks awful, almost like if the cure was worse than the disease, at least judging from snippets such as the above.

Do not take this the wrong way, and I know you get it all working out for yourself in one way or another, but I even wonder how many bugs and how many hours of testing you have to go through simply because you're just hacking around rather than trying to produce good reusable and extensible code, which could make your life a lot easier, and the code less buggy for any change.

I mean, if you open your eyes a bit:

Code: Select all

if (aPawn == None || aPawn.bDeleteMe)
{
    GoTo NothingToDo;
}
else 
{
    //code
}
NothingToDo:
could simply be:

Code: Select all

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

//code

and this is just an example.

And I am just picking on you specifically, because you're the first one raining fire and brimstone over other programmers whenever you see even the most trivial mistakes, yet you yourself are unwilling to do any better, when you obviously could to much better if you were actually willing to.
But then again, to each their own, I just hope that at least I got my point across this time.