Phantasmal MUD Lib for DGD

Phantasmal Site > DGD > DGD LPC Reference > Mappings

Mappings in DGD

Date: Thu, 22 Jul 1999 16:05:28 +0200 (CEST)
From: "Felix A. Croes"
Subject: Re: [DGD]Global/Local Variables

On Thu, 22 Jul 1999, Neil McBride wrote:

>[...]
> Now, my problem is that this variable with the same name is behaving as a global
> variable under certain situations.  Ie, I enter function 1 and the variable x is a
> mapping with two keys.  I run function 2, passing the x as the argument and modify
> the data in function 2. Function 2 then finishes and function 1 continues on.
> However, the value's within the mapping have now changed according to anything that
> was done to function 2's x variable.
>
> Now, as I understand how it's supposed to work, this should _not_ happen unless I'm
> passing pointers around, which I'm clearly not (can you even do that under DGD??)
> Is this the expected behaviour or is there something amiss here?

Not so.  Both mappings and arrays are shared when passed as function
arguments.  If you want to pass a copy, and the mapping is just one
level deep, the following will work:

    foo( map[..] );

Regards,
Dworkin

Date: Wed, 25 Aug 1999 14:23:32 +0200 (CEST)
From: "E. Harte"
Subject: Re: [DGD]A complicated mapping.

On Tue, 24 Aug 1999, Seer Asmodean wrote:

[...]
> mapping table_s ;
> 
> table_s = ([ "Title1": ([ "Subtitle1": ({ 1,
>                                           "Short bit of text.\n",
>                                           "A much longer bit of text.\n" }),
>                           "Subtitle2": ({ 2,
>                                           "Short bit of text.\n",
>                                           "A much longer bit of text.\n" })
>                           ]),
>              "Title2": ([ "Subtitle1": ({ 1,
>                                           "Short bit of text.\n",
>                                           "A much longer bit of text.\n" })
>                           ])
>              ]);
> 
> (That IS a mapping mapped to another mapping mapped to an array.)
[...]
> I'm having several problems with this:
> 1) Where do I declare it, and where do I assign up the values? It's
>    intended to be a constant.

If you want to have one unique mapping like this in the whole 'mud',
you'll want to put it in an object of its own, and query the data from
that one object.  If you put it in a .h file and and #include that .h file
in various objects, you'll be creating different instances of the same
data, there will be no sharing.

> I've tried putting it in a separate .h file as above (but on one long
> line for  the compiler) and including it into player.c. This generates a
> 'Syntax Error' when I run the mud.

Probably because you made a typo in the data, I've corrected two or three
in the quoted text above, one '([' was deleted and a '.' replaced with a
',', compare and see. :)

> I've tried turning it into a #define, the mud will run but any line of code 
> added to the lib to access it will cause the same unhelpful error message.

Same reason, probably. :)

> 2) Assuming I'm not making an impossible request and it can be made to work, 
>    how do I access the various elements?
>    My current asumption would be something like
>    table_s["Title1"]["Subtitle2"](0) to return 2, or
>    table_s["Title1"]["Subtitle2"](1) for "Short bit of text.\n".

You'll want to use table_s["Title1"]["Subtitle2"][0] and [1],
respectively.

> 3) Is it possible to lay such code out like above, i.e split across several 
> lines in a nice coder-readable table? When coding it I put the whole thing on 
> one line,  not knowing how to tell the compiler that it was all one line.

Sure, if you're using a #define then you can use backslashes to continue
on the next line, like this:

 table_s = ([ "Title1": ([ "Subtitle1": ({ 1, \
                                           "Short bit of text.\n", \
                                           "A much longer bit of text.\n" }), \
                           "Subtitle2": ({ 2, \
                                           "Short bit of text.\n", \
                                           "A much longer bit of text.\n" }) \
                           ]), \
              "Title2": ([ "Subtitle1": ({ 1, \
                                           "Short bit of text.\n", \
                                           "A much longer bit of text.\n" }) \
                           ]) \
              ]); \

Hope that helps. :)

Erwin.

Date: Fri, 15 Dec 2000 00:15:51 +0100
From: Erwin Harte
Subject: Re: [DGD]removing elements from mappings

On Thu, Dec 14, 2000 at 11:35:25PM +0100, Boris J wrote:
> Hello !
> 
> I wondering what is the proper way of removing elements (indices/values)
> from a mapping in DGD ?

Several ways.

> Shouldn't this work:
> 
>     foo_mapping -= bar_indices;
> 
> which would remove 'bar_indices' indice and it's value from the
> 'foo_mapping' mapping ?

Yes:

  mapping m;

  m = ([ 1: 2 ]);
  m = m - ({ 1 });

  m is now ([ ])

This does the same thing:

  m -= ({ 1 });

> I discovered also that setting value of an indice to 'nil', e.g.
> 
>     foo_mapping[bar_indices] = nil;
> 
> makes the 'bar_indices' entry to disappear from the mapping. Does this mean 
> that it's impossible to store nil values in DGD mappings, or is it a 
> configurable option ? ( Like changing 'typechecking' in driver config file
> or something... )

Assigning a nil value to an index is another way of removing an
index:value pair from a mapping.  Correct.  If you want to use nil
anyway, you could for instance use 1-sized arrays instead of the
actual values.

The only effect the typechecking option has is that for values 0 and 1
you can accomplish the same thing using 0 instead of nil (more to the
point, nil _is_ 0 in those situations).

Hope that helps,

Erwin.
-- 
Erwin Harte         :    `Don't mind Erwin, he gets crabby. :)'
                    :       -- Par Winzell

From: dgd at list.imaginary.com (Erwin Harte)
Date: Wed Oct 10 10:03:01 2001
Subject: [DGD] mapping question

On Wed, Oct 10, 2001 at 04:44:39PM +0200, Pete wrote:
> Is there a way how to access mapping elements by sequential 
> index? Something other, faster, then using map_indices() and then 
> iterating them and use them as index for mapping.

Doesn't get any faster than this:

  int i, sz;
  mixed *ind, *val;

  sz = map_sizeof(map);
  ind = map_indices(map);
  val = map_values(map);
  for (i = 0; i < sz; i++) {
      /* Do whatever you want with the ind[i] and val[i] pair. */
  }

Hope that helps,

Erwin.
-- 
Erwin Harte

From: dgd at list.imaginary.com (Erwin Harte)
Date: Wed Feb  6 09:06:01 2002
Subject: [DGD] [Melville] non-static mappings

On Wed, Feb 06, 2002 at 08:40:30AM -0600, Erwin Harte wrote:
> On Wed, Feb 06, 2002 at 12:15:05PM +0000, Shevek wrote:
> > 
> > >I do not get your point. In DGD, setting map[key] = nil will remove
> > >the key-value pair from the mapping. This is just as safe as map -=
> > >({ key }).
> > >
> > >// Mikael / Elemel
> > 
> > Are you sure about that?
> 
> I am, my code would fall apart if DGD didn't behave like that.
> 
[...blah...]

Ok, that was totally unrelated to what you asked, but let me assure
you that 'map[key] = nil' has the same effect of 'map -= ({ key })'.

Please, don't let me post before having had my coffee, next time?

Erwin. ;-)
-- 
Erwin Harte

From: dgd at list.imaginary.com (Erwin Harte)
Date: Wed Feb  6 11:41:00 2002
Subject: [DGD] [Melville] non-static mappings

On Wed, Feb 06, 2002 at 04:57:59PM +0000, Shevek wrote:
[...]
> 
> testmap=([ ]);
> testarr=({"test1","test2","test3","test4","test5","test6","test7","test8","test9",});
> testkey=({"key1","key2","key3"});
> 
> /* Populate mapping */
> for(i=0;i<sizeof(testkey);i++){
>         testmap[testkey[i]]=testarr[i*3..i*3+2];
> }
> 
[...]
> 
> /* testmap-=({testkey[1]});*/
> testmap[testkey[1]]=nil;
> 
[...]
> 
> And from the save file:
> testmap ([2|"key1":({3|"test1","test2","test3",}),"key3":({3|"test7","test8","test9",}),])
> 
> Both methods produce exactly the same results.
> Ie Both remove all values associated under the key and the key from the 
> mapping. Neither produced anything unexpected in the save file.

:-)

> Now if I could just figure out why I thought differently :>

Dunno, but I think you would benefit greatly from writing some sort of
'string dump_value(mixed m)' type function, would save you a few dozen
lines in this test alone.

Also, you do know you can easily populate that mapping by just doing:

  testmap = ([
	"key1": ({ "test1", "test2", "test3" }),
	"key2": ({ "test4", "test5", "test6" }),
	"key3": ({ "test7", "test8", "test9" })
  ]);

Right?  That at least would make the program easier to understand,
for me.

Your mileage, etc.

Erwin.
-- 
Erwin Harte

From: dgd at list.imaginary.com (Erwin Harte)
Date: Wed Feb  6 17:38:01 2002
Subject: [DGD] [Melville] non-static mappings

On Thu, Feb 07, 2002 at 12:07:43AM +0100, Mikael Lind wrote:
[...]
> >   map = ([ ob1: 1,
> >            2:   ob2 ]);
> >
> > Now, if either ob1 or ob2 gets destroyed, the relevant index:value
> > pair in the mapping will disappear.
> 
> Is this really true for ob1, though? I would have thought that when
> ob1 is destructed, it would be replaced by nil, which is a valid
> mapping index. So, with both ob1 and ob2 destructed, map would be
> ([ nil: 1 ]).

It's true for ob1 as well.  You could use this feature to easily keep
track of the online players by having a map from <connection-object>
to '1' and only update it when someone (re)enters the game.  If
someone logs out or loses his/her/its link the mapping will
automatically be cleaned out, so less effort there.

Erwin.
-- 
Erwin Harte

From: dgd at list.imaginary.com (Par Winzell)
Date: Tue Mar 19 16:44:01 2002
Subject: [DGD] Memory management

>> To expand on this: for persistent objects (which is all you'll see in 
>> the kernel library) the references to the object are of no 
>> consequence; when the object is destructed, any existing reference to 
>> the object will evaluate to 'nil'.
> 
> 
> Does this include references in mappings and arrays?  The kernel userd 
> object uses a logout method to specifically remove a user from the array 
> and mapping it maintains.  Seems as if this wouldn't be needed, since 
> the user object itself is destructed soon after.

It is not required for the mapping -- destruct objects in a mapping are 
removed from it -- but if you did not remove the user object from the 
array, the array would contain an explicit nil. That's tolerable from 
time to time, of course; any access to this array could just make sure 
to wipe 'nil' values from it. However, in this case, the logout is the 
more well-structured approach, eh?

Zell

From: dgd at list.imaginary.com (Erwin Harte)
Date: Tue Mar 19 16:55:01 2002
Subject: [DGD] Memory management

On Tue, Mar 19, 2002 at 05:07:45PM -0500, Jay Shaffstall wrote:
> 
> >To expand on this: for persistent objects (which is all you'll see in the 
> >kernel library) the references to the object are of no consequence; when 
> >the object is destructed, any existing reference to the object will 
> >evaluate to 'nil'.
> 
> Does this include references in mappings and arrays?  The kernel userd 
> object uses a logout method to specifically remove a user from the array 
> and mapping it maintains.  Seems as if this wouldn't be needed, since the 
> user object itself is destructed soon after.

In arrays they turn to nil as well.  Mappings are special, in the
sense that if an entry in the mapping (index or value or both) refer
to a destructed object, the entry is cleaned out before you see it in
LPC space (i.e: before DGD returns the result of one of the map_*
kfuns or an index into a mapping.

So if you had:

  mapping m;

  m = ([ <someobject>: 1 ]);

And destruct <someobject>, then m will be ([ ]) the next time you look
at it.

Same for this:

  mapping m;

  m = ([ 100: <someobject> ]);

Hope that helps,

Erwin.
-- 
Erwin Harte

From: dgd at list.imaginary.com (Erwin Harte)
Date: Thu Jan  8 07:35:01 2004
Subject: [DGD] Re: another question about clones

On Thu, Jan 08, 2004 at 12:29:54PM +0100, Bart van Leeuwen wrote:
> On Wed, 7 Jan 2004, Par Winzell wrote:
[...]
> > My suggestion is that you let the master copy of the object keep track
> > of all its clones, possibly using a mapping of mappings.
> 
> This is workable and is what I use right now.
> Actually, each 'master' only has to have an array of all its clones, you
> can track all the masters with some kind of daemon still, the same daemon
> can be used to track inheritance of course.

Beware of what will happen when you have more clones of one particular
master object than fits in status()[OST_ARRAYSIZE], which is why Par
mentioned the mapping of mappings. :)

Erwin.
-- 
Erwin Harte

From: dgd at list.imaginary.com (Par Winzell)
Date: Thu Jan  8 09:54:01 2004
Subject: [DGD] Re: another question about clones

On Thu, 2004-01-08 at 09:04, Bart van Leeuwen wrote:
> The choice of mapping or array is not very relevant for this unless you
> are going to use the mapping for other things then tracking clones (one
> could think of using it for keeping track of how a clone was created for
> example). Somehow I assumed an array to be cheaper if all I want is a
> collection of clones (or alternatively a collection of collections of
> clones if it no longer fits in a single array)

If you mean that you use array addition and subtraction whenever you
create a new clone or destruct an old one, then that's not really an
option. Both cloning and destructing N objects then becomes pretty much
a O(N^2) operation. You really want to stay away from those.

Alternately, perhaps you have an array of arrays that you index by the
'clone number' of the clone? That'd solve the time complexity problem,
but use a hell of a lot of space.

Zell

From: dgd at list.imaginary.com (Bart van Leeuwen)
Date: Thu Jan  8 10:46:01 2004
Subject: [DGD] Re: another question about clones

On Thu, 8 Jan 2004, Par Winzell wrote:

> On Thu, 2004-01-08 at 09:04, Bart van Leeuwen wrote:
> > The choice of mapping or array is not very relevant for this unless you
> > are going to use the mapping for other things then tracking clones (one
> > could think of using it for keeping track of how a clone was created for
> > example). Somehow I assumed an array to be cheaper if all I want is a
> > collection of clones (or alternatively a collection of collections of
> > clones if it no longer fits in a single array)
>
> If you mean that you use array addition and subtraction whenever you
> create a new clone or destruct an old one, then that's not really an
> option. Both cloning and destructing N objects then becomes pretty much
> a O(N^2) operation. You really want to stay away from those.

Not really. When an object is destroyed, its value in the array becomes 0,
subtracting 0 from an array before use is still far from O(N^2) and isn't
done at destruction time at all. As a result, the code actually has an
O(1) behavior on destructing objects (for as far as the lpc code is
concerned... cost of the different opperations on driver level is a
different matter)

The only problem is with adding clones, which you can solve in a few ways,
but will come down to finding an array with < MAX elements. You can assume
this to be the array with the highest number and if you start there and
look back before creating a new array, you will get a result that is in
most cases no more then setting an index to a value, and as such hardly
more expensive then when you'd be using a mapping. (note that you do not
have to find the holes in an array, there are none)

If you'd want, you can delay locating such arrays untill they are
completely empty, and just remove 0 size arrays only, but that may not
work out very well in some specific situations (you may end up with a lot
of arrays with 1-2 elements each for example so some sort of merging is
needed to make this one work, also it makes things more expensive
elsewhere)

In my experience, in the end the most important performance issue is
however the time it takes to create a list of all clones and the
complexity of that is way lower with an array of arrays. All other things
can be spread out between when you register/remove a clone and when you
use the data, and in the end it is nice to save a few cycles, but nicer to
spend a few more, but never need too many of them at once.

With mappings you make a few different choices obviously, and I assume
that you wouldn't go for the most expensive approach there either, the
'shortcuts' are just different.

>
> Alternately, perhaps you have an array of arrays that you index by the
> 'clone number' of the clone? That'd solve the time complexity problem,
> but use a hell of a lot of space.
>

You'd be better off implementing that with mappings, and seeing how clone
numbers can rise above the size of an array, it means first doing a bit of
math to derive the indeces

Anyway, regardless of if its more or less efficient, it has been used for
many years and seems to do its job very well.

From: dgd at list.imaginary.com (Par Winzell)
Date: Thu Jan  8 11:02:01 2004
Subject: [DGD] Re: another question about clones

Bart,

>>If you mean that you use array addition and subtraction whenever you
>>create a new clone or destruct an old one, then that's not really an
>>option. Both cloning and destructing N objects then becomes pretty much
>>a O(N^2) operation. You really want to stay away from those.
> 
> Not really. When an object is destroyed, its value in the array becomes 0,
> subtracting 0 from an array before use is still far from O(N^2) and isn't
> done at destruction time at all. As a result, the code actually has an
> O(1) behavior on destructing objects (for as far as the lpc code is
> concerned... cost of the different opperations on driver level is a
> different matter)

I guess this works if you can rely on destruction to remove the object 
(or, rather, to nil it). Skotos uses this kind of indexing system for 
other lists where a clone may need to leave one chain and enter another 
without the convenience of destruction. In that case, you do need to 
search through O(N) elements to remove an object, and while this cost is 
at the driver level, yes, complexity considerations remain similar.


> Anyway, regardless of if its more or less efficient, it has been used for
> many years and seems to do its job very well.

If your solution works, great. Marrach has something like 200k clones of 
/base/obj/thing and I suspect the array solution could result in a 
pretty significant performance problem for us. Whatever. :)  It wasn't 
meant as an attack.

Zell

From: dgd at list.imaginary.com (Noah Gibbs)
Date: Sun Jan 25 03:13:01 2004
Subject: [DGD] copy() or lack thereof in Melville

--- Michael McKiel wrote:
> there is a function called copy: [...]
> which is used, according to the header, as a
> non-recursive copy function for mappings, arrays
> and other variables represented by pointers.

  Yup.  A fine and useful function.  That is, in fact,
just what it does.

> It also
> states, that if the variable itself contains
> pointers,
> that they are _not_ copied. 

  Yes.  So it's a shallow copy, or one-level copy, as
opposed to a full recursive copy.

> Now in various files we will see this function
> finding its way into the code, like users_d
>.c whenever a
> map_indices() or map_values() is returned.

  I'll restate what Steve Schmidt said, but in a
slightly different way.

  In DGD, when you return a mapping, an array or a
lightweight object (LWO), that object is passed by
reference, not by value.  So if you modify elements of
the array or mapping, or call functions on the LWO,
you can modify the original copy.

  However, you can't share data that way for long,
because at the end of the DGD thread of execution the
mapping, array or LWO will be copied over into the
object that has a reference to it.

  An object with security needs should be careful
returning its arrays, mappings or LWOs, because an
unscrupulous caller might change them.  So if you
store a room's exits as an array, don't just return
the array -- if the caller reassigns an element of the
array, he can change what the set of exits is!

  However, it seems odd to me that he tends to call
copy when he returns a map_values or map_indices,
because I think that returns a copy anyway.  So if you
modify it, it won't do anything to the original array
or mapping.

> Places we dont see it:
> ----------------------
> in /system/users_d.c:
> object find_user(string name) {
>     if (!users) users = ([ ]) ;
>     if (member_array(name,map_indices(users))==-1)
>         return 0 ;
>     return users[name] ;
> }

  Yeah.  In this case, returning a single user won't
do anything to the users array.  However, if
users[array] were an array or mapping that could be
modified, we'd want to copy it.  I don't remember
Melville well enough to know whether that's the case.

  If it's an object, we still might want to worry
about security since functions could be called on it,
but we'd handle that kind of security in a different
place, and in a different way.

> in /inherit/object.c:
> string *query_id() { return id ; }
> which returns an array of id names.

  Yeah, copy() should probably be called here to avoid
the caller being able to modify 'id'.

> So my question is, is the copy() function
> needed?

  That functionality is needed, yeah.  Phantasmal, for
instance, would return "id[..]" rather than "id",
which does the same thing that copy() would.  It's a
matter of how you prefer to write it, but it does the
same thing either way.

> is it redundant now in dgd 1.2.7# ? since
> melville was released back in dgd 1.0.# days?

  Nope.  That particular situation hasn't changed any,
except that there are now LWOs, which are more
interesting to copy.  Melville doesn't use them, so
that's not an issue.

> And if it's not
> redundant, then shouldn't everything that
> returns a mapping/array need it?

  Only if the called object retains a reference to
that mapping or array.  For instance, Phantasmal will
sometimes build a new array in response to a function
call.  It hands it back to the caller.  That's fine,
because Phantasmal no longer has a reference to it, so
it no longer cares what happens to it.

> Sorry for this long ramble, not sure how to
> be more concise and still explain.

  No, it's a good question.  Say, have you found my
documentation web site?  It's at
"http://phantasmal.sf.net/DGD".  The answer to this
question is there, though you'd have to read pretty
carefully to find it.  I'll have to add a better
answer to this question while doing the overhaul.

From: dgd at list.imaginary.com (Michael McKiel)
Date: Thu Feb 12 14:08:01 2004
Subject: [DGD] Objectd.c mappings of arrays

I believe Par Winzell metioned mappings of arrays in January during a
discussion of object managers, and another mentioned arrays of arrays. There
was some debate about the operational efficiency of both: O(N) O(N^2) or
such. And mention of Marrach having some 200k+ clones of 'thing.c'

So would it be something like this?
    mapping object_list;
    object *arr1;
    object *arr2;
    object *arr3;

    object_list[0] == ({ arr1 });
    object_list[1] == ({ arr2 });
    object_list[2] == ({ arr3 });

If so, wouldn't you have to have some idea how many of a given object you
will have so you can make sure your arrays/mappings don't grow beyond
MAX_ARR_SIZE? Like in my example above, if my MAX_ARR_SIZE was 2000. Then if
the number of clones of a given item became 6000, the object manager would
fail. So I would have to have a bunch of empty arrays initialized just in
case? Or am I missing something...

From: dgd at list.imaginary.com (Par Winzell)
Date: Thu Feb 12 15:19:01 2004
Subject: [DGD] Objectd.c mappings of arrays

Michael,

> So would it be something like this?
>     mapping object_list;
>     object *arr1;
>     object *arr2;
>     object *arr3;
> 
>     object_list[0] == ({ arr1 });
>     object_list[1] == ({ arr2 });
>     object_list[2] == ({ arr3 });
> 
> If so, wouldn't you have to have some idea how many of a given object you
> will have so you can make sure your arrays/mappings don't grow beyond
> MAX_ARR_SIZE? Like in my example above, if my MAX_ARR_SIZE was 2000. Then if
> the number of clones of a given item became 6000, the object manager would
> fail. So I would have to have a bunch of empty arrays initialized just in
> case? Or am I missing something...

You can do it lots of ways. I believe we use an array of mappings at 
some point in the Skotos library. Having a few empty initialized arrays 
in an object that already stores several megabytes of meta-information 
is so irrelevant it should not even be considered sloppy.

If the objects really are sparse, you could just do a mapping of 
mappings instead -- e.g.

mapping bigmap;

void store_object(ob) {
    int ix;

    ix = status(ob)[O_INDEX];

    if (!bigmap[ix/1024]) {
       bigmap[ix/1024] = ([ ]);
    }

    bigmap[ix/1024][ob] = ob;
}


which would suffice for a good while, and still have excellent 
computational complexity.

Zell

From: dgd at list.imaginary.com (Erwin Harte)
Date: Fri Feb 20 19:35:01 2004
Subject: [DGD] Re: map_indices guaranteed to be alphabetical?

On Fri, Feb 20, 2004 at 05:11:51PM -0800, Noah Gibbs wrote:
>   I've hit a point of contention with another programmer.  I thought
> that map_indices() wasn't guaranteed to return the indices in ASCII
> order.  The other programmer claims it is.  I know it usually does, but
> will it always?

I've built many a program on the assumption that it does.  Rest
assured that a hitman will be hired if Dworkin ever changes that
behaviour. ;-)

Yes, the indices as returned by map_indices() will always be sorted
appropriately within each type.

Erwin.
-- 
Erwin Harte

From: dgd at list.imaginary.com (Erwin Harte)
Date: Thu Dec 13 17:35:01 2001
Subject: [DGD] Mappings

On Thu, Dec 13, 2001 at 03:12:56PM -0800, Chooser Fireman wrote:
> I was wondering if there is a function similar to sscanf that I can
> find a match for partial input in a mapping?
> 
> ie. match attack with the user input of atta with a mapping?

Not in the general case but for that particular one you could use
mapping-subranges:

    string  verb, verb0, *matches;
    mapping verbs;

    verb0 = verb;
    verb0[strlen(verb0) - 1]++;

    matches = map_indices(verbs[verb..verb0]) - ({ verb0 });

This is untested code, so the usual disclaimers apply. ;-)

Erwin.
-- 
Erwin Harte