Dictionary with MobRefs as keys - possible bug?

Here you can post threads specific to the current release of the core (099)

Moderator: POL Developer

Post Reply
OWHorus
Master Poster
Posts: 95
Joined: Sat Feb 04, 2006 1:24 pm
Location: Vienna, Austria

Dictionary with MobRefs as keys - possible bug?

Post by OWHorus » Tue Jan 03, 2017 2:40 pm

Hello,

I am using dictionaries with MobRefs as keys in my AI and elsewhere, and now, with a core from around end of 2016 (not absolutely the last version, but a very current version) I seem to have a problem:

If I create several mobiles ad put their MobRefs into a dictionary, then I can use the dictionary as usual for finding the mobs by MobRef, I can delete specific mobs from the dictionary and so on.
But if a certain mob dies its MobRef cannot be deleted from the dictionary (this is only true for NPCs), it reports an error 'Object does not support members'! It seems to work if this is the last MobRef in the dictionary, and if the mobs are killed in the order they were inserted into the dictionary it also seems to work.

So - is it possible to delete a MobRef from a dictionary if the mob is dead? In my previous version of the core (around 2009 or so) this worked fine.

I have a spell which creates 3 mirror images of the caster. As long as they exist ('live'), the spell cannot be used again. The mirror images are simple identical NPCs, looking like the caster, but never moving or fighting, and they have 3 Hitpoints each. If they are killed, they disappear.
To control this, I add the 3 MobRefs to a dictionary, and afterwards check every 5 seconds, if the mobs still live. If a mob is dead, then I delete them using this loop:

Code: Select all

	// mirrors is a dictionary, containing MobRefs of previously created mobs (NPCs)
	while (mirrors.size() > 0)
		sleep(4);
		foreach mirror in (mirrors.Keys())
			if (!(mirror.dead == 0))			// This check works for player chars and NPCs
				res := mirrors.erase(mirror);	// Erase the MobRef (MobRef is the key)
				if (!res)					// This error check I added after finding, that it no longer works
					print(res.errortext);
				endif
			endif
		endforeach
		sleep(1);
	endwhile								// If all mobs do not exist, the spell ends and can be cast again
The mobs in this spell disappear ('die') after a certain time, or they can be killed before that.

I think this could be a bug, since allowing MobRefs (as keys) in a dictionary should also allow erasing the keys (old MobRefs) of dead mobs. The MobRef exists, or the check mob.dead would no longer work.

Could somebody look into this? I will test further in the next few days...

OWHorus

Nando
POL Developer
Posts: 260
Joined: Wed Sep 17, 2008 6:53 pm
Contact:

Re: Dictionary with MobRefs as keys - possible bug?

Post by Nando » Tue Jan 03, 2017 3:08 pm

A quick work-around is to use their serials instead of a mobref.

Anyway, this is odd. Have you tried deleting dead mobiles manually, outside a loop?

Also, could you check what is returned by the method keys(), and what is inside the variable mirror?

And could you print(CStr(res))?

OWHorus
Master Poster
Posts: 95
Joined: Sat Feb 04, 2006 1:24 pm
Location: Vienna, Austria

Re: Dictionary with MobRefs as keys - possible bug?

Post by OWHorus » Wed Jan 04, 2017 2:26 pm

Hello,

I checked this and it seems to be a bug. I used the following test script (written, so that you should be able to compile and use it too):
(You must change the AI script to a script which exists. It is helpful if the NPCs do not wander around).

Code: Select all

use uo;
use basic;
use os;
use util;

CONST TIMEOUT := 120;
CONST N_NPCS := 2;

program testmobrefdict(who)
	var mobiles := dictionary;
	var res;
	var aborttime := ReadGameClock() + TIMEOUT;

	// Create N_NPCS numbered mobiles, they are numbered in the sequence of creation
	// Add the MobRefs to the dictionary, in the order of creation
	// Using Template of an human male (0x190), and a simple script which does nothing (stone just stands still)
	// NPCs are create in a line before you facing N - check if there is space!
	
	// Props - just for the script, and the name
	var props := struct {"name", "script"};
	props.script := "stone";

	for n := 1 to N_NPCS
		props.name := "NPC"+CStr(n);
		var mob := CreateNpcFromTemplate("N0190", who.x - 1 + n, who.y-2, who.z, props, who.realm);
		if (!mob)
			syslog("Could not create NPC: "+mob.errortext);
			exit;
		endif
		mobiles.insert(mob, n);
	endfor

	// Now this waits, until all the NPCs are dead and removed from the dictionary
	// TIMEOUT - ends the script forcefully, if something goes wrong
	// Time limit TIMEOUT seconds, loop runs every 5 seconds
	// When all NPCs are killed, the loop ends, it end forcefully after TIMEOUT seconds even if not empty
	while (mobiles.size() > 0)
		sleep(4);
		print("Keys = "+mobiles.keys());
		foreach mob in (mobiles.Keys())
			if (!(mob.dead == 0))
				syslog("Killed: "+CStr(mobiles[mob]));
				res := mobiles.erase(mob);
				if (!res)
					print(res.errortext);
					print(CStr(res));
					print("Error with MobRef - not removed!");
				endif
			else
				syslog("Mob exists and is alive: "+CStr(mobiles[mob]));
			endif
			if (ReadGameClock() >= aborttime)
				if (mobiles.size() > 0)
					print(mobiles);
					syslog("Timeout Testscript: Dictionary not empty");
					exit;
				endif
			endif
		endforeach
		print(mobiles);
		sleep(1);
	endwhile
	syslog("Script ends normally");
	SendSysMessage(who, "Script ended normally");
endprogram
The script is simple: It creates 2 NPCs (N_NPCS can be changed to use more), and it has a timeout TIMEOUT seconds, so that the script is aborted if the dictionary cannot be emptied. Normally the test script stops as soon the dictionary is empty.

The script adds the MobRef of each created NPC to the dictionary, using MobRef as a key, and the creation number (1,...) as value. Then it runs a loop every 5 seconds, printing the dictionary. I also uses the MobRefs to print information, namely the creation number. You can also see the number in the dictionary printouts.

Now - when I kill NPC 1 first, then NPC 2, all seems to be normal. When I kill NPC 2, and then NPC 1, something happens. Here the results from the console:

Example 1: Kill 1, then kill NPC 2

Code: Select all

syslog [scripts/textcmd/gm/poltestmobrefs.ecl]: Mob exists and is alive: 2
syslog [scripts/textcmd/gm/poltestmobrefs.ecl]: Mob exists and is alive: 1
dict{ <appobj:MobileRef> -> 2, <appobj:MobileRef> -> 1 }
syslog [scripts/textcmd/gm/poltestmobrefs.ecl]: Mob exists and is alive: 2
syslog [scripts/textcmd/gm/poltestmobrefs.ecl]: Mob exists and is alive: 1
dict{ <appobj:MobileRef> -> 2, <appobj:MobileRef> -> 1 }
--- KILL NPC 1
syslog [scripts/textcmd/gm/poltestmobrefs.ecl]: Mob exists and is alive: 2
syslog [scripts/textcmd/gm/poltestmobrefs.ecl]: Killed: 1
dict{ <appobj:MobileRef> -> 2 }
--- KILL NPC2
syslog [scripts/textcmd/gm/poltestmobrefs.ecl]: Killed: 2
dict{  }
syslog [scripts/textcmd/gm/poltestmobrefs.ecl]: Script ends normally
Example 2: Kill NPC 2, then kill NPC 1

Code: Select all

syslog [scripts/textcmd/gm/poltestmobrefs.ecl]: Mob exists and is alive: 2
syslog [scripts/textcmd/gm/poltestmobrefs.ecl]: Mob exists and is alive: 1
dict{ <appobj:MobileRef> -> 2, <appobj:MobileRef> -> 1 }
--- KILL NPC 2
syslog [scripts/textcmd/gm/poltestmobrefs.ecl]: Killed: <uninitialized object>
error{ errortext = "Object does not support members" }
0
Error with MobRef - not removed!
syslog [scripts/textcmd/gm/poltestmobrefs.ecl]: Mob exists and is alive: <uninit
ialized object>
dict{ <appobj:MobileRef> -> 2, <appobj:MobileRef> -> 1 }
--- THE LOOP STILL TRIES TO REMOVE THE MOBREF, SINCE THE NPC TESTS AS DEAD< AND IT FAILS AGAIN
syslog [scripts/textcmd/gm/poltestmobrefs.ecl]: Killed: <uninitialized object>
error{ errortext = "Object does not support members" }
0
Error with MobRef - not removed!
syslog [scripts/textcmd/gm/poltestmobrefs.ecl]: Mob exists and is alive: <uninit
ialized object>
dict{ <appobj:MobileRef> -> 2, <appobj:MobileRef> -> 1 }
--- KILL NPC 1
syslog [scripts/textcmd/gm/poltestmobrefs.ecl]: Killed: 2
syslog [scripts/textcmd/gm/poltestmobrefs.ecl]: Killed: <uninitialized object>
error{ errortext = "Object does not support members" }
0
Error with MobRef - not removed!
--- AND NOW THE DICT IS EMPTY, DESPITE THE ERROR MESSAGE, IT REMOVED NPC2 AND REPORTED FAILURE FOR NPC1, BUT IS EMPTY NOW
dict{  }
syslog [scripts/textcmd/gm/poltestmobrefs.ecl]: Script ends normally
As you can see, killing NPC 2 does something to the MobRef2 of NPC 2, so that it becomes unusable (uninit). It also does something to the MobRef1 of NPC1, because I cannot read its value, even if it never was deleted! And the dictionary does not allow to remove MobRef2!
But if I kill NPC1 too, it suddenly is possible to remove MobRef2 (which still reports mob.dead), and a receive the error while removing MobRef1, but it is actually removed, since the dictionary is empty afterwards and the script ends normally.

The same happens with more than 2 NPCs, if you kill them in the order its MobRefs were created an added, removing works normally, but if I start with killing NPC2 or NPC3, things go south. I tried it with 5, but the logs are too long to post here, and there are many variations. But sure is, that if I kill in the same order the NPCs were created and added, it works, else it does not. And it seems, that the dictionary is - despite error messages - empty after killing (and trying to remove) the last NPC.

Interestingly the MobRefs remain valid, since I can test with mob.dead. But dict[MobRef] becomes invalid, so the MobRefs seem to be okay, but the dictionary has a problem somehow.

I hope this helps.

EDIT: I just remembered your request to look at mobiles.keys() - and added a line 'print("Keys = "+mobiles.keys());'. The script above is edited.

This shows (I did not change the results in console in this post): If I kill NPC1, then NPC2, it returns correct mobiles.keys() array, but if I kill NPC2, then NPC1 (the error case), the array seems to become invalid! Before killing a NPC mobiles.keys() is normal, after killing NPC2 (before NPC1) it is no longer printed! It seems to be invaid, and since I read it in every loop pass via mobiles.keys(), it seems to be defective permanently. I just hope, this is not the case with all dictionaries, this would be really bad...
OWH



OWHOrus

Turley
POL Developer
Posts: 644
Joined: Sun Feb 05, 2006 4:45 am

Re: Dictionary with MobRefs as keys - possible bug?

Post by Turley » Thu Jan 05, 2017 10:00 am

The short answer is never ever use objects as dictionary keys :P
The longer answer is that we will have fun during fixing... :(
The problem is that the complete container moves into the deep water of undefined state. Thus you have luck that even one case works.
I don't know why it was allowed in the first place to use such objects in a dictionary, but I guess we have to keep the feature and try to fix it.
I would suggest to use a simply array for your storage and use the bool operator to find dead ones.

OWHorus
Master Poster
Posts: 95
Joined: Sat Feb 04, 2006 1:24 pm
Location: Vienna, Austria

Re: Dictionary with MobRefs as keys - possible bug?

Post by OWHorus » Thu Jan 05, 2017 12:30 pm

Hello Turley,

thank you - I did use this because of the easy 'no search' way to remove certain entries from the dictionary. Using serials instead of MobRefs is promising in the case of 'mirrors', since it is easy to check if a mobile still 'lives' or is dead.

On the other hand - the MobRefs remain active, after the mobile dies (I am speaking of NPCs only, because a player char mobile remains fully active even if 'dead'). But the MobRef seems to be active as long it is stored inside a script. I can refer to it and check the NPC template, and the state 'dead'. But something was changed somewhere so that the dictionary is damaged or becomes invalid, if a 'dead' MobRef is inside.

Using serials is possible (no array necessary, dictionary should work fine). Referring to the serial will give the 'dead' state just fine. I just have a lot of work - again - to change a lot in my code, since using MobRefs was just fine for years, and it was 'legal', i.e. documented and it should work. I am using it in my AI, where the AI stores current 'enemies' as Mobrefs, and if an enemy dies, or leaves the area cleanup is simple and fast. But it should be nearly as fast to do it with serials...

So - if it is too hard to fix it, change the documentation, and that is it.

OWHorus

Turley
POL Developer
Posts: 644
Joined: Sun Feb 05, 2006 4:45 am

Re: Dictionary with MobRefs as keys - possible bug?

Post by Turley » Thu Jan 05, 2017 1:01 pm

I think you misunderstood me.
Using mobilerefs or other refs somewhere else is perfectly fine, even after destruction. Since they will be kept alive as long as a script references it.
The problem only starts if you store such references in a dictionary as key.
The deeper I look into that code I wonder why not everyone complains about it since pol095 (or even before no idea since when it is allowed).
Once one object get destroyed the complete container is undefined.

Thus keep your mobile ref storage in your AI (I would say every shard does this) but never ever store it in a dictionary as a key. Use for this specific task a simple array.

Or wait till we fixed it (it's a bit more to fix, so could take it's time)

OWHorus
Master Poster
Posts: 95
Joined: Sat Feb 04, 2006 1:24 pm
Location: Vienna, Austria

Re: Dictionary with MobRefs as keys - possible bug?

Post by OWHorus » Thu Jan 05, 2017 1:44 pm

Yes, I get it, I think :-)

I will try to change it all to serials (which as integers can be stored in a dictionary). Normally a certain NPC (at this time referred to with a MobRef) can easily removed from a dictionary, but not from an array, because I need to search for it, determine the index and erase it. In a dictionary I can directly erase it. But the same functionality can be done with the serial. The disadvantage is, that a serial from a MobRef is easy (MobRef.serial), an MobRef from a serial is not easy, since I need to SystemFindObjectBySerial().

In my AI - using MobRefs - I can take a MobRef from the dictionary, and do for example Distance (me, MobRef), or Walkto (MobRef), and so on. This is the reason I designed it with MobRefs in dictionaries. But it should be not that bad, and I can remove 'dead' MobRefs at the same time, since SysFind() will tell me that it does not exist any more. It seems to work until around 2009, which was the core I used for years. After upgrading to a a core from around June 2016 or so it obviously stopped to work.

The problem is, such a problem is not seen immediately. All seems to work, but lately somebody using my Mirror-Spell told me, that in certain cases the spell did not correctly detect, that one Mirror-Image was still present ('alive'). So he could recast it, and had 4 Mirrors, and so on. You could have a lot of Mirror-NPCs, and this was not as planned. That way I found, that it does no longer work. The Mirror-Spell is now fixed (serials instead of MobRefs in a dictionary). The AI will take a while, but it seems to run with this problem, not entirely correct, but good enough ;-)

OWHorus

Post Reply