RFC: Lightweight Objects

58 views
Skip to first unread message

Gnomi

unread,
Nov 6, 2020, 11:34:57 AM11/6/20
to Discussion of the LDMud driver.
Hello everyone,

we are thinking about introducing a new data type into the LPC language.
Lightweight objects are compiled from LPC files similar to objects, but
they won't have a name, environment, inventory, heartbeats, call_outs or
shadows. They cannot be living or interactive. They live as long as there
are references to them and will automatically be destructed.

I want to present our ideas here to implement lightweight objects, so we
can hear your opinions on them before we start implementating. Although
the following text might sound definitive, all points are open for discussion.

Lightweight objects will be created with a new efun:

lwobject ob = new_object("/obj/whatever");

They'll have a type distinct from regular objects, because their feature set
is very different from regular objects. And so to catch any mistakes early
they have their own type. If you have a better name for the type or the efun
I'd like to hear about it.

To create a lightweight object, the given file is loaded. The file will exist
as a normal blueprint (regular object). The file must have a
#pragma lightweight
so that it can be instantiated as a lightweight object. This pragma will
activate the pragma no_clone and no_shadow as well and might prohibit
the use of set_environment() on the blueprint.

Lightweight objects however can inherit regular objects and vice versa.

Lightweight objects cannot be destructed, but they can be copied with copy()
and saved with save_value() (or save_object() as part of an object's variable).
Their save format will be similar to structs. Although lightweight objects
won't have a name on their own, load_name() and program_name() will work
(object_name() therefore will not work).

Lightweight objects have a UID/EUID and can call other objects or do file
operations. This means that functions like this_object(), previous_object()
or caller_stack() will return either an object or lwobject. Also master
applies (like valid_read and valid_write) will have to deal with lwobjects
as well.

A call to lightweight objects can be done using the efuns call_other,
call_strict, call_resolved and their _direct counterparts, as well as with
the -> and . operator. As there are no default functions, there is no
difference between call_other and call_direct. If the compiler can determine
that one of these efuns or operators is used exclusively on lightweight
objects, it will remember the whereabouts of the called function and thus
offer nearly the same performance as internal function calls.

We are also thinking about extending the type system for regular and
lightweight objects to specify a mandatory program in the inherit structure:
object "/i/room" room;
lwobject "/obj/whatever" myob;
The compiler will not be able to check those at compile time, but the runtime
type checks will verify them.

Although we have put some thought into all these points, they are still open
for discussion, so we'd like to hear your comments on them.

Regards,
Gnomi

Karl Tiedt

unread,
Nov 6, 2020, 12:54:57 PM11/6/20
to Discussion of the LDMud driver.
Name wise, I'd think something less verbose would work, "simple object", "limited object", "base (or basic?) object"...  I suppose these will have significant performance benefits in terms of resource usage?

Definitely sounds like an interesting addition, I'd be curious to hear what some of the initial ideas were that sparked the idea?

-Karl Tiedt


--
You received this message because you are subscribed to the Google Groups "LDMud Talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ldmud-talk+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ldmud-talk/20201106163454.GA16261%40platinum.motzkau.

Zesstra

unread,
Nov 6, 2020, 1:35:58 PM11/6/20
to ldmud...@googlegroups.com
On 06.11.20 18:54, Karl Tiedt wrote:
> Definitely sounds like an interesting addition, I'd be curious to hear what
> some of the initial ideas were that sparked the idea?
I think, I suggested something in this direction around 2006... It was the
wish to join code with the corresponding encapsulated data into an object that
can be instantiated but with less overhead than the normal objects, which
interact with in a complex way with environments, network and other real objects.
So, the idea was something like "just a bunch of code + data and (if possible)
optimized external calls into them. But since the lwobjects should be able to
perform disc IO and call other objects, they need a little bit more for access
rights management.

Bye,
Zesstra@MG
--
MorgenGrauen -
1 Welt, mehr als 200 Programmierer , mehr als 16000 Raeume,
viel mehr als 7000 unterschiedliche Figuren, 90 Quests, 13 Gilden,
ueber 5000 Waffen und Ruestungen, keine Umlaute und ein Haufen Verrueckter.
Existenz: mehr als 25 Jahre
http://mg.mud.de/

Gnomi

unread,
Nov 6, 2020, 2:04:43 PM11/6/20
to ldmud...@googlegroups.com
Hello Karl,

Karl Tiedt schrieb:
> Name wise, I'd think something less verbose would work, "simple object",
> "limited object", "base (or basic?) object"...

'simple object' or 'base object' could lead to some confusion, because when
we teach LPC, then the first objects a beginner encounters are the full
blown objects. For a beginner these are normal and sometimes very simple
objects. 'limited' might also be misleading, because automatic destruction
might not be a limitation, but a feature. And in our ticket system there are
already further ideas for such objects like operator overloading, so they
might not be limited, but just offer another feature set.

> I suppose these will have
> significant performance benefits in terms of resource usage?

Normal objects have large memory and runtime overhead. They need to manage
reset and cleanup times, call_outs, heartbeats, inventory, environments,
shadows, interactive connections. External function calls need to check
shadows and default functions, and look up a function name in the function
table. We try to cut that down to offer performance like an internal
function call.

> Definitely sounds like an interesting addition, I'd be curious to hear what
> some of the initial ideas were that sparked the idea?

This is a really old idea. We have a ticket that is over 20 years old
referring to that idea. (https://mantis.ldmud.eu/mantis/view.php?id=311,
here for details in a room/on an item, that can be looked at.)

They'll bridge the gap between structs and regular objects. I often
encounter cases, where I would like to transfer and encapsulate data.
With structs you cannot hide the contents. And additional functions
to work with these structs must always be provided beside the structs.

Real objects can be used, but they have two disadvantages: The have a big
overhead and they do not destruct automatically. Especially the later
disqualifies them to be used in/as data structures that are passed around,
then you'll always need someone who is responsible for their lifetime.

Instead we still pass around arrays or mappings (for example for room exit
information) and need to call functions in the original object to change
them (the data is of course copied when querying, so they cannot be modified
outside...). So basically in a regular mudlib object orientation just stops
at the level of a room or item. And with lightweight objects we want to go
deeper and offer object orientation for data structures and algorithms.

Regards,
Gnomi.

Stephan Weinberger

unread,
Nov 6, 2020, 2:31:49 PM11/6/20
to ldmud...@googlegroups.com

On 06.11.20 17:34, Gnomi wrote:
> Hello everyone,
>
> we are thinking about introducing a new data type into the LPC language.
> Lightweight objects are compiled from LPC files similar to objects, but
> they won't have a name, environment, inventory, heartbeats, call_outs or
> shadows. They cannot be living or interactive. They live as long as there
> are references to them and will automatically be destructed.


Sounds interesting, I can think of various useful applications for this,
e.g. any type of master object, or any sort of reusable code that can be
loaded at runtime (i.e. loadable modules). Also this would allow to
implement certain design patterns more efficiently (visitor, command,
...). Lots and lots of possibilities.

> lwobject ob = new_object("/obj/whatever");

Why not just new()?


> To create a lightweight object, the given file is loaded. The file will exist
> as a normal blueprint (regular object).
So basically just like clone() but only creating a copy of only the
internal variables, without any of the surrounding overhead?

> The file must have a
> #pragma lightweight

Maybe lets do it the other way around: don't use a pragma at all, but
just distinguish the type from the way the object is created (just like
it's already the case with blueprints & clones).
Is there anything preventing such lightweight objects be created via
load_object()/clone()? If they don't use their inv/env anyways (because
there usually is none) this should make no difference (except for the
additional overhead of having the unused inv/env). Inversely, regular
objects created via new() would just be presented an empty inv/zero env;
and obviously using it as a destination in move_object would raise a
runtime error, as would using heartbeat/shadow/call_out.
Maybe there are useful applications for using the same program in
different roles (just like with blueprints and clones).


> Lightweight objects however can inherit regular objects and vice versa.
Hence there needs to be a way to handle the use of an empty inv/env
anyways, as those are likely to be referenced in regular objects...

> We are also thinking about extending the type system for regular and
> lightweight objects to specify a mandatory program in the inherit structure:
> object "/i/room" room;
> lwobject "/obj/whatever" myob;
> The compiler will not be able to check those at compile time, but the runtime
> type checks will verify them.

Also an interesting idea.


regards,
  Invisible

Travis Everett

unread,
Nov 6, 2020, 2:35:27 PM11/6/20
to ldmud...@googlegroups.com
I think lightweight communicates the advantage better than simple/limited/base/basic. If the "<x> object" form is a given, maybe "thin" or "slim"?

Conceptually I can see something that focuses on ~immateriality, but in practice all of the ones I've come up with feel too vague (latent, intangible, formless, abstract) or risk being interpreted as fantasy-worldy.

The example I've had in my head for several years is something like the ability to parse an XML into a DOM and represent each node with an object. I wrote such a thing at one point, but recall the object construction overhead being too high. I don't recall if this came from public discussion, or if I was just reading into the idea--I don't see this specific case mentioned on the issue tracker.

Stephan Weinberger

unread,
Nov 6, 2020, 2:40:31 PM11/6/20
to ldmud...@googlegroups.com

On 06.11.20 20:35, Travis Everett wrote:
>
> The example I've had in my head for several years is something like
> the ability to parse an XML into a DOM and represent each node with an
> object.

Another useful application. Also the whole parser object could be passed
around and/or exchanged, i.e. you could have a simple way to make your
code read/write multiple formats or dont use files at all but a database
instead just by loading a different lwobject to do the job.

This is of course also possible today, but as you say the overhead makes
it rather impractical.

regards
  Invisible


Karl Tiedt

unread,
Nov 6, 2020, 2:44:05 PM11/6/20
to Ldmud Talk
Gnomi,


'simple object' or 'base object' could lead to some confusion, because when
we teach LPC, then the first objects a beginner encounters are the full
blown objects. For a beginner these are normal and sometimes very simple
objects. 'limited' might also be misleading, because automatic destruction
might not be a limitation, but a feature. And in our ticket system there are
already further ideas for such objects like operator overloading, so they
might not be limited, but just offer another feature set.

I don't see this is a blocking issue... as things evolve, so should the training around it. Beginners encounter these objects first because there are only these objects. As far as limited being misleading, it would only become misleading if they were later extended to support full object features again... In fact, from a learning perspective, these new things would make sense to learn first, building up to the more intensive and interactive pieces.

limited: 
restricted in size, amount, or extent; few, small, or short. -- ie: a restricted subset of object. 

If confusion is a key concern, I would think it key to find a name other than object to use for them, but that may be an even bigger challenge.
 
-Karl Tiedt


--
You received this message because you are subscribed to the Google Groups "LDMud Talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ldmud-talk+...@googlegroups.com.

Stephan Weinberger

unread,
Nov 6, 2020, 3:21:54 PM11/6/20
to ldmud...@googlegroups.com


On 06.11.20 20:43, Karl Tiedt wrote:

limited: 
restricted in size, amount, or extent; few, small, or short. -- ie: a restricted subset of object. 

If confusion is a key concern, I would think it key to find a name other than object to use for them, but that may be an even bigger challenge.


more suggestions: particle, iota, atom, crumb :-)

Gnomi

unread,
Nov 6, 2020, 4:10:54 PM11/6/20
to ldmud...@googlegroups.com
Hi,

Stephan Weinberger wrote:
> On 06.11.20 17:34, Gnomi wrote:
> > To create a lightweight object, the given file is loaded. The file will exist
> > as a normal blueprint (regular object).
> So basically just like clone() but only creating a copy of only the
> internal variables, without any of the surrounding overhead?

Correct.

> > The file must have a
> > #pragma lightweight
>
> Maybe lets do it the other way around: don't use a pragma at all, but
> just distinguish the type from the way the object is created (just like
> it's already the case with blueprints & clones).
> Is there anything preventing such lightweight objects be created via
> load_object()/clone()? If they don't use their inv/env anyways (because
> there usually is none) this should make no difference (except for the
> additional overhead of having the unused inv/env). Inversely, regular
> objects created via new() would just be presented an empty inv/zero env;
> and obviously using it as a destination in move_object would raise a
> runtime error, as would using heartbeat/shadow/call_out.
> Maybe there are useful applications for using the same program in
> different roles (just like with blueprints and clones).

The pragma technically would not be necessary. As I imagine the
implementation you could create a lightwight object from a regular
blueprint. The pragma has two advantages and one disadvantage.

The first advantage is the prevention of mix-ups of clone_object() and
new_object() (or whatever that efun will be named). I estimate that the
majority of cases will be single-use programs (either lightweight or
regular), and thus it will prevent errors (leaked objects). The second
advantage is that the compiler could offer some warnings when object efuns
(like environment()) are used in a lightweight object.

The disadvantage is that it will prevent dual-use programs. I couldn't
imagine any use-case, so if you'll have one, I would be glad to hear it.
Such a use-case would still be possible you'll make the program an inherit
for two programs (one object, one lwobject), but of course this is a hurdle
if that's needed on a regular basis.

I would like to hear more opinions on the pragma.

> > Lightweight objects however can inherit regular objects and vice versa.
> Hence there needs to be a way to handle the use of an empty inv/env
> anyways, as those are likely to be referenced in regular objects...

Right. But the compiler could remember whether a program used some
object-only features and warn if it is inherited in an lwobject program.
(And if there are any lwobject-only features, then also vice versa.)
At runtime you'll of course get an error.

Regards,
Gnomi.

Stephan Weinberger

unread,
Nov 6, 2020, 4:25:22 PM11/6/20
to ldmud...@googlegroups.com

On 06.11.20 22:10, Gnomi wrote:
> The disadvantage is that it will prevent dual-use programs. I couldn't
> imagine any use-case, so if you'll have one, I would be glad to hear it.
> Such a use-case would still be possible you'll make the program an inherit
> for two programs (one object, one lwobject), but of course this is a hurdle
> if that's needed on a regular basis.

You mentioned that the idea was born out of objects or details in a
room. What if that room also can clone that object (e.g. because the
player takes it) - with dual-use you could then just create a clone of
the very same object and only need to implement it once (and any changes
are instantly reflected in the room as well as the cloned object).

E.g. something like this:

---------------------------------------------------------

lwobject thing = new("thingybob.c");

...

string look_thing() {
  return thing->description();
}

int take_thing() {
  return move_object(clone(thing), this_player());
}

---------------------------------------------------------

regards,
  Invisible

Gnomi

unread,
Nov 6, 2020, 4:50:40 PM11/6/20
to ldmud...@googlegroups.com
Hello Stephan,

Stephan Weinberger wrote:
> On 06.11.20 22:10, Gnomi wrote:
> > The disadvantage is that it will prevent dual-use programs. I couldn't
> > imagine any use-case, so if you'll have one, I would be glad to hear it.
> > Such a use-case would still be possible you'll make the program an inherit
> > for two programs (one object, one lwobject), but of course this is a hurdle
> > if that's needed on a regular basis.
>
> You mentioned that the idea was born out of objects or details in a
> room. What if that room also can clone that object (e.g. because the
> player takes it) - with dual-use you could then just create a clone of
> the very same object and only need to implement it once (and any changes
> are instantly reflected in the room as well as the cloned object).

Okay, I understand your use case, but do you see that as a likely one?

In our mudlib we have real and virtual items. And the real items have a huge
overhead, that is not the LPC object overhead. Real items have move / weight /
value logic, have actions. Our most simple real item has 358 functions and
50 variables. This was the reason why virtual items were invented in the
first place. That's why I would avoid having the same program for virtual
and real items.

So is it worth it to support that use case?

Regards,
Gnomi.

Stephan Weinberger

unread,
Nov 6, 2020, 5:15:43 PM11/6/20
to ldmud...@googlegroups.com

On 06.11.20 22:50, Gnomi wrote:
>
> So is it worth it to support that use case?


Idk if it's worth it, it was just the first thing that sprang into my
mind after you mentioned the history of the idea :-)

You could basically very cheaply use the real thing as "virtual object"
in your case. (You could of course just use the blueprint as well,
depending on how things are implemented.)


I'd just rather not restrict functionality without really needing to;
especially when you have to handle edge cases anyways because you allow
inheriting regular objects - then you can just as well always allow it.


"avoiding mixups between new_object and clone_object" isn't a good
argument IMHO. If someone mixes up those calls they might just as well
mix up clone_object and load_object, which will also have unintended
consequences. If they only use a new_object within the calling object
then it makes little difference (apart from the unnecessary overhead);
and as soon as they try to move it into an environment they'll get a
runtime error anyways.

Maybe just find a name for the new-function, that makes it more clear
that that thing is only meant for "internal use" of the current object.



Another way to enforce the correct use would be the return type of that
function, i.e. really introduce a new type 'lwobject' (or atom, crumb,
thingy, ... whatever). Then this could easily be checked at compile time
- just like a pragma, but without the restrictions of a pragma.

By extension it would even make sense to also do this with blueprints
and clones, and define 'object' as 'blueprint|clone|thingy') - this
would not break old code but would allow typesafe code in the future.


regards,

  Invisible



Zesstra

unread,
Nov 7, 2020, 6:03:52 AM11/7/20
to ldmud...@googlegroups.com
On 06.11.20 23:15, Stephan Weinberger wrote:
> "avoiding mixups between new_object and clone_object" isn't a good
> argument IMHO. If someone mixes up those calls they might just as well
> mix up clone_object and load_object, which will also have unintended
> consequences.

I personally think it is a very good reason because of its practical impact.
Years ago we had in Morgengrauen a huge problem with cloned objects that were
leaked. Millions of them. It took a lot of work and we hammered home the
message that you never instantiate an object without moving it into an
environment.
This paradigm will be weakened by the lwobjects. If due to old habits people
use clone_object instead of new_object (as I am sure they will) we will fall
back into the old problems.
Using new_object instead of clone_object will be "safe", but not the other way
around. And this can't be prevented by type-checking (alone).
Since leaked objects will be ultimately my problem as admin, I currently do
prefer that 'object o = clone_object("lwobject_file.c");' will trigger an error.

The comparison with load_object is not valid in my view: while that causes
problems, they are easily noticed and load_object can only create one object
(and it anyway needs to exist). clone_object is able to create millions of
leaked clones.

Bye,
Zesstra

Stephan Weinberger

unread,
Nov 7, 2020, 9:23:03 AM11/7/20
to ldmud...@googlegroups.com

On 07.11.20 12:03, Zesstra wrote:
> Years ago we had in Morgengrauen a huge problem with cloned objects that were
> leaked. Millions of them. It took a lot of work and we hammered home the
> message that you never instantiate an object without moving it into an
> environment.
This could be easily mitigated by having a simul_efun for clone_object,
which accepts an additional parameter for the target object (defaulting
to the current object) and automatically moves the clone. When people
have to explicitely write efun::clone_object they should start to think
about what they're doing ;-)

> This paradigm will be weakened by the lwobjects. If due to old habits
> people
> use clone_object instead of new_object (as I am sure they will) we will fall
> back into the old problems.
> Using new_object instead of clone_object will be "safe", but not the other way
> around. And this can't be prevented by type-checking (alone).
> Since leaked objects will be ultimately my problem as admin, I currently do
> prefer that 'object o = clone_object("lwobject_file.c");' will trigger an error.

Ok, but we already have a pragma for that: no_clone. If that's what
you're trying to achieve why not use it? No need to introduce a new one
that more or less does the same.

In this regard lwobjects behave just like blueprints (except each
program that uses them get its own local copy), so why treat them
differently?


regards,

  Invisible


Zesstra

unread,
Nov 7, 2020, 9:58:02 AM11/7/20
to ldmud...@googlegroups.com
On 07.11.20 15:22, Stephan Weinberger wrote:
>> Years ago we had in Morgengrauen a huge problem with cloned objects that were
>> leaked. Millions of them. It took a lot of work and we hammered home the
>> message that you never instantiate an object without moving it into an
>> environment.
> This could be easily mitigated by having a simul_efun for clone_object,
> which accepts an additional parameter for the target object (defaulting
> to the current object) and automatically moves the clone. When people
> have to explicitely write efun::clone_object they should start to think
> about what they're doing ;-)

Well, you could easily mitigate the restriction that light-weight objects can
not be instantiated by clone_object.
It depends on your definition of "easily". Yes, I know. And you can heap
additional overhead on traditional objects, even refcount and auto-destruct
them on LPC side. But that is not the point.
You wish for an edge case in a new feature and ask me and a lot of people to
adjust a mud and program overhead for the common case of an old feature.

>> Since leaked objects will be ultimately my problem as admin, I currently do
>> prefer that 'object o = clone_object("lwobject_file.c");' will trigger an error.
>
> Ok, but we already have a pragma for that: no_clone. If that's what
> you're trying to achieve why not use it?

You could use it, but in reality people will forget to set it, because they
are not forced to do that. We could require that no_clone is set for
new_object() to work, but that is something you do not want...

I wish for a mechanism to explicitly opt-in (to be a light-weight object), you
are asking for a mechanism to explicitly opt-out (to be a standard object).
I also wish for a mechanism that actively marks an object as "light-weight
object ready" so that I have a guarantee that I can only successfully
instantiate them if the programmer has prepared it for being that. (most of
the usual objects won't be prepared.)

> In this regard lwobjects behave just like blueprints (except each
> program that uses them get its own local copy), so why treat them
> differently?

I disagree that lwobjects are like blueprints. They are ref-counted and
auto-cleaned clones.

Gnomi

unread,
Nov 7, 2020, 10:23:39 AM11/7/20
to ldmud...@googlegroups.com
I have a suggestion to allow dual-use programs, but keep single-use the
default: Introduce positive and negative pragmas.

So then we have pragma no_clone, clone, no_lightweight and lightweight.

#pragma no_clone: Disallow clone_object()
#pragma clone: Allow clone_object(), implies no_lightweight
#pragma no_lightweight: Disallow new_object()
#pragma lightweight: Allow new_object(), implies no_clone

The implied pragmas can be overridden by explicitely stating the
opposite pragma.

Default will be clone, no_lightweight.

So in effect you will have the following combinations (optional/implied
pragmas in brackets):

no_clone [no_lightweight]: Program can only be used as a blueprint
[clone] [no_lightweight]: Program can be cloned
[no_clone] lightweight: Program can be used as an lwobject.
clone lightweight: Program can be cloned and used as an lwobject

I'm a little bit worried about the complexity (for teaching that).
What's your opinion on that?

Regards,
Gnomi.

Invisible

unread,
Nov 7, 2020, 11:50:02 AM11/7/20
to ldmud...@googlegroups.com

On 07.11.20 15:58, Zesstra wrote:
> You wish for an edge case in a new feature and ask me and a lot of
> people to
> adjust a mud and program overhead for the common case of an old feature.

I can't quite follow you here.

I merely pointed out how the problem you had with the "old featureset"
could have been solved using "old features". I'm not asking anyone to
adjust anything, as the "common case" will work exactly the same as it
did before.


>> Ok, but we already have a pragma for that: no_clone. If that's what
>> you're trying to achieve why not use it?
> You could use it, but in reality people will forget to set it, because they
> are not forced to do that. We could require that no_clone is set for
> new_object() to work, but that is something you do not want...

Well, I think that a new type is sufficient to "force" people to use the
new feature wisely, without restricting the use of lwobjects (e.g. in
the way I proposed earlier). Of course this isn't a "common case" *yet*,
because that feature simply doesn't exist yet.


> I wish for a mechanism to explicitly opt-in (to be a light-weight object), you
> are asking for a mechanism to explicitly opt-out (to be a standard object).
> I also wish for a mechanism that actively marks an object as "light-weight
> object ready" so that I have a guarantee that I can only successfully
> instantiate them if the programmer has prepared it for being that. (most of
> the usual objects won't be prepared.)

As I already pointed out: you have this problem anyways, as soon as you
allow to inherit any other program in a lwobject. Those inherits usually
won't be prepared to be used in a lwobject either. So to avoid this
you'd have to restrict the use of the new feature even further and only
allow inheritance of other lwobjects.

It's a philosophical question, of course, if you want to force a
specific use of a specific feature, or if you let the programmers decide
how to use it. I'd prefer the latter.


>
>> In this regard lwobjects behave just like blueprints (except each
>> program that uses them get its own local copy), so why treat them
>> differently?
> I disagree that lwobjects are like blueprints. They are ref-counted and
> auto-cleaned clones.

What I meant with "in this regard" was from a usage and leakage
standpoint. If you "hammered home the message" that clones have to be
moved into an environment (or 'encourage' this by providing a
simul_efun) then a lwobject cannot be used like a clone; the move will
always fail, hence sefun::clone_object will always fail. Ergo,
functionally, it is closer to a blueprint. (And from what I understand
that's what a lwobject would be anyways: a blueprint with a minimal set
of copied state to allow for separate instances.)

And btw: this cannot be detected at compile time anyways, as
clone_object() takes a string argument and only processes it at runtime.
So a pragma actually doesn't really help you in this context.


regards,

  Invisible

Invisible

unread,
Nov 7, 2020, 12:45:16 PM11/7/20
to ldmud...@googlegroups.com

On 07.11.20 16:23, Gnomi wrote:
> So in effect you will have the following combinations (optional/implied
> pragmas in brackets):
>
> no_clone [no_lightweight]: Program can only be used as a blueprint
> [clone] [no_lightweight]: Program can be cloned
> [no_clone] lightweight: Program can be used as an lwobject.
> clone lightweight: Program can be cloned and used as an lwobject
>
> I'm a little bit worried about the complexity (for teaching that).
> What's your opinion on that?


Feels a bit like overkill to me. I think we agree that inadvertently
creating a lwobject can cause little harm, as its lifetime is so
restricted anyways and any attempt to use it like a clone is very likely
to throw errors. The opposite, inadvertently creating a clone, doesn't
introduce any new problems. The main problem seems to be leaks; well...
"no_clone" is an existing solution for that, as is enforcing a move (be
it by a simul_efun or by policy), or providing a proper H_CLEAN_UP hook
that removes non-interactive clones without environment (and prints
debug messages, so you can easily notice a leak).


But on the other hand having options never hurts. Personally, out of
these "no_lightweight" would be the one that makes the most sense to me,
because it's the direct complement to "no_clone".

I guess the complexity in the driver wouldn't be the problem, as it's
only one additional flag to be switched on or off (and you have to
implement it anyways if you want any variant of pragma). As for
"teaching that": the best would of course be to provide (configurable)
default settings; either via a hook (that way you could even e.g. set it
depending on the filename and have your own subdirectory for lwobjects
if you like) or simply as a compiletime or commandline option for the
driver. For MG I guess Zesstra would set the default to
"clone"+"no_lightweight" and teach the use of "no_clone" and
"lightweight"; in RoleMUD I'd rather set "clone"+"lightweight" as
default. In both cases there's just one new pragma to teach... plus, if
you teach the use of new() and/or introduce a new data type you'd have
to teach that in any case; having two more possible combinations of
flags doesn't really change that much here.


regards,
  Invisible


Gnomi

unread,
Nov 9, 2020, 7:10:45 AM11/9/20
to Discussion of the LDMud driver.
Hello everyone,

Thank you for your feedback. I'd like to give an overview about the current
state of the discussion as I see it. The overall feedback has been positive.
Only a few minor issues have been under discussion:

1. Name of the subject matter
-----------------------------
Original proposal: lightweight object, in LPC 'lwobject'
Suggestions: simple object, limited object, base object, basic object,
thin object, slim object, particle, iota, atom, crumb

No suggestion catched my eye. The LDMud team voiced against limited or
base object. And beside the proposers there were no voices for any of
the suggestions.

2. Name of the constructor efun
-------------------------------
Original proposal: new_object()
Suggestion: new()

The were no additional voices for this suggestion.

3. Control of construction
--------------------------
Original proposal: A new pragma required for lightweight objects

Suggestion 1: Reuse no_clone pragma (allow all cloneable objects).
The LDMud team felt strongly against this suggestion.

Suggestion 2: Introduce 3 new pragmas for a more fine-grained control.
Invisible sees that as overkill, but he thinks it's better then
the original proposal and suggests configurable defaults.

Regards,
Gnomi.

Invisible

unread,
Nov 9, 2020, 1:25:35 PM11/9/20
to ldmud...@googlegroups.com

On 09.11.20 13:10, Gnomi wrote:
> Hello everyone,
>
> Thank you for your feedback. I'd like to give an overview about the current
> state of the discussion as I see it. The overall feedback has been positive.
> Only a few minor issues have been under discussion:

What about my "add-on-proposal" to also split the 'object' type into a
union of specialized subtypes for blueprints, clones and lwobjects? Or
do we stick to the existing objectp()/clonep() plus then lwobjectp()
mechanism?

How deep are unions embedded into the driver? Is this currently just
syntactic sugar for function calls or does the concept already run
through the whole codebase?

> 2. Name of the constructor efun
> -------------------------------
> Original proposal: new_object()
> Suggestion: new()

I'd like to add: "new_object" would be quite confusing when the returned type is explicitly *not* 'object'. At least call it new_lwobject then (or whatever name you choose for the new type).


regards,
Invisible


Gnomi

unread,
Nov 9, 2020, 1:49:46 PM11/9/20
to ldmud...@googlegroups.com
Hello Invisible,

Invisible wrote:
> On 09.11.20 13:10, Gnomi wrote:
> What about my "add-on-proposal" to also split the 'object' type into a
> union of specialized subtypes for blueprints, clones and lwobjects? Or
> do we stick to the existing objectp()/clonep() plus then lwobjectp()
> mechanism?

In the original proposal lwobject was to be a distinct type.
Therefore I mistook your suggestion to just split blueprints and clones,
which would be entirely independent of lightweight objects and therefore
be another discussion.

If I understand you correctly (now) you want to have 'object' being a union
to include 'lwobjects' and for real objects introduce two new types.
I'd like to hear opinions on this.

I did similar things for the unicode implementation, where there is a pragma
that makes the string type a union of string|bytes. This was for easing the
transition. In our mudlib we never used that, because it hides errors.
For lightweight objects this is not needed, because existing mudlibs keep
running, because they don't need to use lwobjects.

> How deep are unions embedded into the driver? Is this currently just
> syntactic sugar for function calls or does the concept already run
> through the whole codebase?

The new type system is used in the complete codebase with the exception of
the efuns functionlist(), variable_list() and struct_info(), where the
internal types are converted back to old-school integers.

> > 2. Name of the constructor efun
> > -------------------------------
> > Original proposal: new_object()
> > Suggestion: new()
>
> I'd like to add: "new_object" would be quite confusing when the returned
> type is explicitly *not* 'object'. At least call it new_lwobject then (or
> whatever name you choose for the new type).

Indeed I talked with Zesstra this afternoon about the same idea and we
both prefer new_lwobject().

Regards,
Gnomi

Invisible

unread,
Nov 9, 2020, 1:54:24 PM11/9/20
to ldmud...@googlegroups.com

On 09.11.20 13:10, Gnomi wrote:
> 3. Control of construction
> --------------------------
> Original proposal: A new pragma required for lightweight objects
>
> Suggestion 1: Reuse no_clone pragma (allow all cloneable objects).

just to be sure: "reuse" doesn't sound like what I actually meant :-)
I didn't suggest to "reuse" no_clone in a new, different way. What I was
saying is, that 'no_clone' already provides enough protection against
the errors Zesstra is afraid of (accidentally _cloning_ lwobjects). So
we just *keep* using it for what it is meant to do.

A 'no_lightweight' pragma would keep the paradigm of "preventing
unintended use if necessary"; that's why I feel it makes more sense than
the other way around.


regards,

  Invisible


P.S. please also make the name of the pragma(s) match the type name you
finally choose :-)


Invisible

unread,
Nov 9, 2020, 3:24:40 PM11/9/20
to ldmud...@googlegroups.com

On 09.11.20 19:49, Gnomi wrote:
> Hello Invisible,
>
> Invisible wrote:
>> On 09.11.20 13:10, Gnomi wrote:
>> What about my "add-on-proposal" to also split the 'object' type into a
>> union of specialized subtypes for blueprints, clones and lwobjects? Or
>> do we stick to the existing objectp()/clonep() plus then lwobjectp()
>> mechanism?
> In the original proposal lwobject was to be a distinct type.
> Therefore I mistook your suggestion to just split blueprints and clones,
> which would be entirely independent of lightweight objects and therefore
> be another discussion.
>
> If I understand you correctly (now) you want to have 'object' being a union
> to include 'lwobjects' and for real objects introduce two new types.
> I'd like to hear opinions on this.


Yes, that's what I originally meant:

# typedef object = blueprint|lwobject|clone


My thinking was, that in most cases a 'lwobject' behaves just like a
'object', so it would somehow make sense to be able to use it everywhere
where 'object' is expected, and only change the signature of the few
efuns where it doesn't make sense (e.g. move_object, all
inventory/environment efuns, ...) to 'blueprint|clone' explicitly.

But I understand that from the driver's perspective clones and
blueprints are basically the same data structure; both of them are
full-fledged "heavy weight" objects with an inventory etc. (and I guess
most Libs also use them that way, i.e. treat blueprints as kind of
singleton objects; I know we do in RoleMUD). So in this context
introducing a separate lwobject type and changing all relevant efuns to
accept 'object|lwobject' does indeed make much more sense.




But another detail that just came to my mind: how will
efun::this_object() or efun::previous_object() work with lwobjects? I
guess they'll just return 0, correct? If so, this would make the use of
normal programs as lwobjects (or as inherits for a lwobject) virtually
impossible anyways, as they are typically littered with calls to those
efuns. Unless of course "typedef object = blueprint|lwobject|clone" :-/


regards,
  Invisible

Gnomi

unread,
Nov 9, 2020, 4:12:13 PM11/9/20
to ldmud...@googlegroups.com
Hi,

Invisible wrote:
> But another detail that just came to my mind: how will
> efun::this_object() or efun::previous_object() work with lwobjects? I
> guess they'll just return 0, correct? If so, this would make the use of
> normal programs as lwobjects (or as inherits for a lwobject) virtually
> impossible anyways, as they are typically littered with calls to those
> efuns. Unless of course "typedef object = blueprint|lwobject|clone" :-/

I wrote about in in the original proposal:

Lightweight objects have a UID/EUID and can call other objects or do file
operations. This means that functions like this_object(), previous_object()
or caller_stack() will return either an object or lwobject. Also master
applies (like valid_read and valid_write) will have to deal with lwobjects
as well.

So theses efuns will change their declaration to be <object|lwobject>.

The alternative would have been to make LWOs invisible in the caller stack.
Then they either could not have rights at all or would inherit the rights
from their callers or creators. We thought that these alternatives would
limit their usefulness and even introduce security risks.

Regards,
Gnomi

Invisible

unread,
Nov 9, 2020, 4:20:26 PM11/9/20
to ldmud...@googlegroups.com

On 09.11.20 22:12, Gnomi wrote:
>
> So theses efuns will change their declaration to be <object|lwobject>.


Ah ok, missed that.


But still this will create a mess if a program that uses

# object ob = this_object();

is inherited by (or used as) a lwobject, wont it?

And I guess this used literally everywhere in libs.


cu
  Invisible

Richard James Salts

unread,
Nov 9, 2020, 9:58:07 PM11/9/20
to Discussion of the LDMud driver.
On Saturday, 7 November 2020 3:34:54 AM AEDT Gnomi wrote:
> Hello everyone,
>
> we are thinking about introducing a new data type into the LPC language.
> Lightweight objects are compiled from LPC files similar to objects, but
> they won't have a name, environment, inventory, heartbeats, call_outs or
> shadows. They cannot be living or interactive. They live as long as there
> are references to them and will automatically be destructed.
>
> I want to present our ideas here to implement lightweight objects, so we
> can hear your opinions on them before we start implementating. Although
> the following text might sound definitive, all points are open for
> discussion.
>
It sounds like the distinction is that a regular object is embodied in the mud
and the new proposal is for a disembodied object that doesn't get all of the
support to interact in traditional LPMud specific ways.

Could this overhead be instantiated when required, for instance if everything
was lightweight until configure_object(foo,OC_EMBODY,1) was run and/or
clone_object() or set_environment() were called on the object, or does it have
to be done at creation time? Could it be done in something like the
H_CREATE_OB hook where you could for instance go with the traditional embodied
object in directories named rooms,monsters,items, etc and the like and not for
others like daemon,server,etc?

>
> Regards,
> Gnomi




Gnomi

unread,
Nov 10, 2020, 5:51:25 AM11/10/20
to ldmud...@googlegroups.com
Hi Richard,

Richard James Salts wrote:
> Could this overhead be instantiated when required, for instance if everything
> was lightweight until configure_object(foo,OC_EMBODY,1) was run and/or
> clone_object() or set_environment() were called on the object, or does it have
> to be done at creation time? Could it be done in something like the
> H_CREATE_OB hook where you could for instance go with the traditional embodied
> object in directories named rooms,monsters,items, etc and the like and not for
> others like daemon,server,etc?

Is this a question or suggestion?

If it's a question: There is no implementation, yet. In an implemention -
how I envision it according to the original proposal - it might be
problematic, because they would have different internal data structures.
LWOs will have a very compact (one memory block) data structure, regular
objects on the other hand have a more complex structure. When you convert
from an regular object to an LWO or vice versa then you need to create a
new structure and copy the variables over and need to replace all pointers
to the old structure. And the last part is hard, because we don't keep
track where a pointer to an object was stored. (As with every computer
science problem you can solve that with an additional level of indirection,
but it would not be elegant.)

If it's a suggestion: I think this is one of the key differences in the
vision between the LDMud team and Invisible in this discussion. We see
LWOs as having a different usage than normal objects. We see their use in
algorithms and data structures that until now have been done imperatively
with arrays, mappings and structs. So we think LWOs will be programs that
do not exist in mudlibs, yet. They might get additional features that
regular objects won't. And this is why we are going for a strict
distinction in the type system.

You on the other hand seem to envision LWOs that are a variation of regular
objects with just automatic lifetime management. So LWOs and regular objects
can be used interchangeably and here even converted at will. And this calls
for a weak (or none) type distinction.

I think it will be hard to implement both. So we need to have a discussion
about it.

Regards,
Gnomi.

Invisible

unread,
Nov 10, 2020, 1:10:45 PM11/10/20
to ldmud...@googlegroups.com

On 10.11.20 11:51, Gnomi wrote:
> If it's a question: There is no implementation, yet. In an implemention -
> how I envision it according to the original proposal - it might be
> problematic, because they would have different internal data structures.
> LWOs will have a very compact (one memory block) data structure, regular
> objects on the other hand have a more complex structure. When you convert
> from an regular object to an LWO or vice versa then you need to create a
> new structure and copy the variables over and need to replace all pointers
> to the old structure. And the last part is hard, because we don't keep
> track where a pointer to an object was stored. (As with every computer
> science problem you can solve that with an additional level of indirection,
> but it would not be elegant.)

It could be solved without indirection by having the LWO as a "base"
data structure that holds a pointer to the "extended" structure of a
full fledged object (which is NULL for LWOs). I.e. the LWO structure
holds the parts of the data that are needed in both types. But then of
course you constantly have to check that pointer to decide which type
the structure currently represents, so I don't think you'd gain much in
terms of complexity or performance. Also I guess that this would require
major changes in teh driver.

It's an interesting concept though and I think we should discuss
possible applications (like the discussion about cloneable vitems we had
earlier - this somewhat overlaps from a user perspective) and
implications for existing code.
But it feels to me like somewhat of a paradigm shift of how objects are
created; currently they are explicitly loaded (=blueprint) or cloned
(=clone), so introducing an "upgrade" would be a quite different way to
manage objects. Although one could argue that, functionally, clones are
just "upgrades" from blueprints (I don't know the data structure, but
I'd imagine that a clone is basically only distinguished by having a
clone-number).

But in general I think such major changes are outside the scope of this RFC.

regards,
  Invisible


Gnomi

unread,
Nov 10, 2020, 1:30:12 PM11/10/20
to ldmud...@googlegroups.com
Hi Invisible,

Invisible wrote:
> On 10.11.20 11:51, Gnomi wrote:
> > If it's a question: There is no implementation, yet. In an implemention -
> > how I envision it according to the original proposal - it might be
> > problematic, because they would have different internal data structures.
> > LWOs will have a very compact (one memory block) data structure, regular
> > objects on the other hand have a more complex structure. When you convert
> > from an regular object to an LWO or vice versa then you need to create a
> > new structure and copy the variables over and need to replace all pointers
> > to the old structure. And the last part is hard, because we don't keep
> > track where a pointer to an object was stored. (As with every computer
> > science problem you can solve that with an additional level of indirection,
> > but it would not be elegant.)
>
> It could be solved without indirection by having the LWO as a "base"
> data structure that holds a pointer to the "extended" structure of a
> full fledged object (which is NULL for LWOs).

As I said the LWO would be a compact data structure, having header and
variables in the same memory block. For regular objects the variables
are in a different block, because the variable block is exchanged
sometimes (replace_program()) or swapped out. That's why LWOs
(as I envision it according to the original proposal) cannot be
a base class for regular objects.

But even if it were possible, Richard wanted to go from LWO to regular
object with a function call. That means, the memory for the possibly
bigger regular object must already be there around the LWO. (If it's not you
need to allocate another block and thus change all pointers, which we tried
to avoid.) And that would make the lightweight objects not that much
lightweight anymore.

Regards,
Gnomi.

Gnomi

unread,
Nov 11, 2020, 5:51:25 AM11/11/20
to ldmud...@googlegroups.com
Hi,

Gnomi wrote:
> Invisible wrote:
> > It could be solved without indirection by having the LWO as a "base"
> > data structure that holds a pointer to the "extended" structure of a
> > full fledged object (which is NULL for LWOs).

I need to correct myself. In the other email I was talking about real base
classes. The suggestion here with the additional pointer is exactly the
additional level of indirection I was talking about earlier.
(I was thrown off by the introduction that the solution doesn't involve
indirection, but indeed suggests indirection via pointer.)

Regards,
Gnomi.

Invisible

unread,
Nov 11, 2020, 9:47:25 AM11/11/20
to ldmud...@googlegroups.com

On 11.11.20 11:51, Gnomi wrote:
> I need to correct myself. In the other email I was talking about real
> base
> classes. The suggestion here with the additional pointer is exactly the
> additional level of indirection I was talking about earlier.
> (I was thrown off by the introduction that the solution doesn't involve
> indirection, but indeed suggests indirection via pointer.)

fair point, I was confused by your remark that you "don't keep track
where a pointer to an object was stored", so I was mentally on the track
of 'back-references'.

Anyway: As I already said I think a change like that - while an
interesting concept - is far outside the scope of the current RFC.


regards,

  Invisible

Gnomi

unread,
Nov 16, 2020, 9:18:07 AM11/16/20
to ldmud...@googlegroups.com
Hi,

Invisible wrote:
> On 09.11.20 22:12, Gnomi wrote:
> > So theses efuns will change their declaration to be <object|lwobject>.
>
> But still this will create a mess if a program that uses
>
> # object ob = this_object();
>
> is inherited by (or used as) a lwobject, wont it?

If you have activated runtime typechecks then you'll get errors there.

> And I guess this used literally everywhere in libs.

I have looked at our base and extended lib and found very few cases and most
of them were right in assuming they are a real object (for example programs
helping in dealing with environment changes or for use in living objects).

My fear is more about previous_object(), those errors will reduce the number
of objects an lwobject can call. But I regard this as a feature, because
existing programs were written with the assumption that the caller is a real
object and might use efuns that only are allowed for real objects. Thus LWOs
should only call objects that are approved for LWOs.

I can offer a pragma to make 'object' really being 'object|lwobject' similar
to the no_bytes_type pragma, but I wouldn't use that in our MUD, because it
hides errors (indeed that's the only effect of this pragma). And as nearly
every other pragma, such a pragma will fragment the language further
(i.e. code witten with the pragma might not run without it). That's why I'm
hesitant about it.

Regards,
Gnomi.

Invisible

unread,
Nov 16, 2020, 10:18:16 AM11/16/20
to ldmud...@googlegroups.com
On 16.11.20 15:18, Gnomi wrote:
>
>> # object ob = this_object();
> I have looked at our base and extended lib and found very few cases and most
> of them were right in assuming they are a real object (for example programs
> helping in dealing with environment changes or for use in living objects).
>
> My fear is more about previous_object(), those errors will reduce the number
> of objects an lwobject can call. But I regard this as a feature, because
> existing programs were written with the assumption that the caller is a real
> object and might use efuns that only are allowed for real objects. Thus LWOs
> should only call objects that are approved for LWOs.


How about a completely different approach:

As far as I understood LWOs can only be used within the context of
another program (i.e. the one that calls "new_lwobject()") - why not use
this program as a result of this_object()? Then the only requirement
would be that a string of new_lwobject-calls has to originate in a
program of type 'object'. Accordingly previous_object() could return the
last object in the call stack that is of type 'object'.

Consequently there should be new efuns this_lwobject() and
previous_lwobject() in this case.


But then again the whole problem arises from 'lwobject' being a
different type than 'object', which brings me back to thinking that
"typedef object = <blueprint|clone|lwobject>" might be a good idea after
all. A LWO behaves just like current objects in most ways (except for
having no inv/env and a different lifetime), so it would make sense for
it to have a compatible type.


regards,

  Invisible

Gnomi

unread,
Nov 16, 2020, 11:53:55 AM11/16/20
to ldmud...@googlegroups.com
Hi Invisible,

Invisible wrote:
> How about a completely different approach:
>
> As far as I understood LWOs can only be used within the context of
> another program (i.e. the one that calls "new_lwobject()") - why not use
> this program as a result of this_object()? Then the only requirement
> would be that a string of new_lwobject-calls has to originate in a
> program of type 'object'. Accordingly previous_object() could return the
> last object in the call stack that is of type 'object'.
>
> Consequently there should be new efuns this_lwobject() and
> previous_lwobject() in this case.

This is what I wrote in an earlier email: hiding LWOs in the call stack.
This is a security risk, especially without runtime type checks (but even
with RTTCs there are functions accepting 'mixed'). You could give an object
an LWO, the object calls LWO->fun() and then the LWO calls
other_ob->secure_fun(). Then it would seem, that the original object would
have called secure_fun(), which it didn't.

In such a setting you could not allow LWOs to call other objects or do file
operations.

Another option would be that for lwobjects its creator appears in the call
stack instead. That would mean an LWO always inherits the rights of its
creator. This might be fine if both programs have the same author and
maintainer. You then must always make sure your LWO comes from a trusted
source. If not, then you are no longer in control of your rights.

> But then again the whole problem arises from 'lwobject' being a
> different type than 'object', which brings me back to thinking that
> "typedef object = <blueprint|clone|lwobject>" might be a good idea after
> all. A LWO behaves just like current objects in most ways (except for
> having no inv/env and a different lifetime), so it would make sense for
> it to have a compatible type.

I want to stress another point that I was trying to make in this discussion.
LWOs are not merely a subset of regular objects. They'll have features of
their own. They are copyable, they can be saved as part of a program's
variable or via save_value(), similar to structs. They might also get
additional features like operator overloading.

LWOs have some commonalities with regular objects as they have with structs.
But they are compatible with neither.

Regards,
Gnomi.

Invisible

unread,
Nov 30, 2020, 4:39:08 AM11/30/20
to ldmud...@googlegroups.com
On 11.11.20 11:51, Gnomi wrote:
>
> I need to correct myself. In the other email I was talking about real base
> classes.


Reading this again I think we might have got stuck in a rut regarding
the name of the new type. Wouldn't the appropriate nomenclature simply
be "class"? From what I understand this would be exactly what this new
type actually is :-) ('objects' are a slight extension of the class
concept imho, since they come with implicit external references like
environment, inventory, etc. - i.e. they are not strictly self-contained)

Sticking to the established concept of blueprints and clones, plus what
we discussed about the (in)compatibility of the new type, this would
suggest the following new efuns:

class load_class(string)
    load a class blueprint and return a pointer to it (basically a
singleton instance)

class clone_class(string|class)
    create a new instance of the class program
    alternative: class instanciate_class(string|class) - sounds a bit
unwieldly imho

int classp(mixed)
    return 1 if the type is 'class'

int clonep(mixed)
    return 2 if the type is 'class' and it is a clone/instance
    alternative: int instancep(mixed)

int|string save_class(string[, int])
int restore_class(string)
    'object'-compatible save/restore
    this would also offer a way to cleanly convert between classes and
objects
    alternative: consolidate those into generic save()/restore() efuns


new pragmas would then simply be:

#pragma class
    program can only be loaded with "load_class()"

#pragma object
    the default

#pragma no_clone
    cannot be cloned/instanciated
    alternative: #pragma no_instance


Plus of course new driver hooks for load/clone_class, etc.


Under the premise of a largely incompatible new type this seems the
cleanest approach to me and most consistent with the existing concepts
of LPC.

regards,
    Invis

Gnomi

unread,
Nov 30, 2020, 5:00:01 PM11/30/20
to ldmud...@googlegroups.com
Hi Invisible,

Invisible schrieb:
> Reading this again I think we might have got stuck in a rut regarding the
> name of the new type. Wouldn't the appropriate nomenclature simply be
> "class"?

I haven't found a similar concept like the proposed type in any other
language. That makes it really hard to find a name for it.

Also that's why I think 'class' is not a preferable term, because it is
an established term in other programming languages. Wikipedia says:

In object-oriented programming, a class is an extensible program-code-
template for creating objects, providing initial values for state (member
variables) and implementations of behavior (member functions or methods).

In LPC the corresponding term is 'program'. It is the template for
creating objects with the implementation and initial values for the
variables. Introducting the term 'class' which will have a different meaning
than our 'program' will be very confusing.

> From what I understand this would be exactly what this new type
> actually is :-) ('objects' are a slight extension of the class concept imho,
> since they come with implicit external references like environment,
> inventory, etc. - i.e. they are not strictly self-contained)

I have a different understanding, an object is to a class as a house is to a
blueprint or a cake to a recipe. One is the set of rules, the other is the
incarnation of it.

Regards,
Gnomi.

Invisible

unread,
Dec 1, 2020, 7:02:33 AM12/1/20
to ldmud...@googlegroups.com
On 30.11.20 22:59, Gnomi wrote:
> In LPC the corresponding term is 'program'. It is the template for
> creating objects with the implementation and initial values for the
> variables. Introducting the term 'class' which will have a different meaning
> than our 'program' will be very confusing.
>

When working with LPC I pretty much think of "program" and "blueprint"
interchangeably for all practical purposes; as a user you rarely
interact with programs by themselves. To me clones very much feel like
"instances" of a blueprint. A speciality of LPC is that the blueprint
itself is also a (different kind of) instance - pretty much like a
singleton, without the hassle. Other languages offer that as well, of
course, when you can at least access static methods of classes; but
uninstantiated classes typically lack a data segment, so it's usually
not as seamless as in LPC.


And actually I like that concept of blueprints & clones very much, so -
regardless of how we end up calling the feature - I'd really appreciate
having it replicated in the new type, i.e. have separate "load" and
"clone" efuns to create blueprint-lwobjects and further instances of it.
That might even simplify the implementation, because that mechanism
already exists. Also #pragma no_clone could be re-used without confusion.


What do you think about the other ideas mentioned in my post? Just
replace "class" with "lwobject" (which I still find a bit unwieldy) for now.

IMHO a generic save/restore (or save_lwobject/restore_lwobject that
returns/accepts the known serialized format) would be an elegant way to
bridge the gap to a fully object-compatible type without all the
headache of actually implementing it. If we e.g. go back to our musings
about v_items: a lwobject-v_item could offer most of the interactivity
while it is still "embedded" in the room, and when a player takes it all
current values could easily be copied into a newly cloned object. If you
still want to allow inheriting everything in objects and lwobjects, this
could also easily be implemented by simply having a "wrapper"-lwobject
that inherits the object's code - so both the object and lwobject would
automatically implement the same interfaces.

regards,
  Invis

Gnomi

unread,
Dec 1, 2020, 8:05:11 AM12/1/20
to ldmud...@googlegroups.com
Hi,

Invisible wrote:
> And actually I like that concept of blueprints & clones very much, so -
> regardless of how we end up calling the feature - I'd really appreciate
> having it replicated in the new type, i.e. have separate "load" and "clone"
> efuns to create blueprint-lwobjects and further instances of it. That might
> even simplify the implementation, because that mechanism already exists.
> Also #pragma no_clone could be re-used without confusion.

I don't understand the use-case of lightweight blueprints. As lwobjects
in the current proposal have no name, there is no difference between a
blueprint lwo and cloned lwos. Also because of the automatic destruction
blueprint lwos are not guaranteed to persist, so the second access to
the blueprint via load_lwobject() might bring you a brand new object,
not the one you had earlier.

> What do you think about the other ideas mentioned in my post? Just replace
> "class" with "lwobject" (which I still find a bit unwieldy) for now.
>
> IMHO a generic save/restore (or save_lwobject/restore_lwobject that
> returns/accepts the known serialized format) would be an elegant way to
> bridge the gap to a fully object-compatible type without all the headache of
> actually implementing it. If we e.g. go back to our musings about v_items: a
> lwobject-v_item could offer most of the interactivity while it is still
> "embedded" in the room, and when a player takes it all current values could
> easily be copied into a newly cloned object. If you still want to allow
> inheriting everything in objects and lwobjects, this could also easily be
> implemented by simply having a "wrapper"-lwobject that inherits the object's
> code - so both the object and lwobject would automatically implement the
> same interfaces.

Technically this would be no problem. It might be confusing to have two save
formats for the same data type (save_object vs. save_value), but I see your
use-case here.

Regards,
Gnomi.

Gnomi

unread,
Dec 1, 2020, 8:35:51 AM12/1/20
to Discussion of the LDMud driver.
Hello everyone,

the topic of most concern for us about the new type is the name of it.

The main point of it is whether it should have 'object' in its name,
like 'lightweight object' (other suggestions were simple object,
limited object, base object, basic object, thin object and slim object),
or whether it should be an entirely different name like particle, iota,
atom, crumb, class, composite, thingy, capsule or pod.

The argument for having 'object' in its name is that there is a big
overlap with regular objects: The are both build from a program. And because
they'll have code to execute they will come up in several efuns as
this_object(), set_this_object(), previous_object(), map_objects(),
filter_objects(). Also the use of save_object() and restore_object()
was suggested for them.

The argument against having 'object' in its name is to distinguish them from
regular objects clearly, because the new type will not merely be a subset of
regular objects but have a feature set of their own, and so to make it clear
that it is a type of its own right. There will be several efuns with object
in its name that won't accept the new type like clone_object(),
configure_object(), find_object(), move_object(), object_info(),
object_name(), object_time(), rename_object() or tell_object().

So what are your opinions on it? What name is more intuitive, easier to
teach?

Regards,
Gnomi

Invisible

unread,
Dec 1, 2020, 8:52:34 AM12/1/20
to ldmud...@googlegroups.com
On 01.12.20 14:05, Gnomi wrote:

> there is no difference between a
> blueprint lwo and cloned lwos. Also because of the automatic destruction
> blueprint lwos are not guaranteed to persist, so the second access to
> the blueprint via load_lwobject() might bring you a brand new object,
> not the one you had earlier.

True, i forgot about the lifetime constraint and thought about using
LWOs as e.g. master objects to reduce overhead.

In this case maybe it shouldn't be called "load" (as this has a
different meaning in the context of objects, where it always returns the
same instance, as opposed to a new one for lwobjects). Maybe
new_lwobject, or create_lwobject(), or make_lwobject()?


>> IMHO a generic save/restore (or save_lwobject/restore_lwobject that
...
> Technically this would be no problem. It might be confusing to have two save
> formats for the same data type (save_object vs. save_value), but I see your
> use-case here.

Hmm, did you plan to create a new serialisation format for a complex
structure like a LWO? IMHO here the same behavior as with objects (does
save_value even work on objects? on 3.3.720 it returns 0, which is
undocumented btw.) would make more sense.

But I'd actually appreciate a usable save_value(object|lwobject) as
well; that would have made the full user&inventory saving in RoleMUD
much easier *g*.


regards,

  Invis

Gnomi

unread,
Dec 1, 2020, 8:56:57 AM12/1/20
to ldmud...@googlegroups.com
Hi,

Invisible wrote:
> Hmm, did you plan to create a new serialisation format for a complex
> structure like a LWO? IMHO here the same behavior as with objects (does
> save_value even work on objects? on 3.3.720 it returns 0, which is
> undocumented btw.) would make more sense.

This was in the original proposal and there were no voices against it, yet:

Lightweight objects cannot be destructed, but they can be copied with copy()
and saved with save_value() (or save_object() as part of an object's variable).
Their save format will be similar to structs.

This is one of the features that LWOs have and objects won't.

Regards,
Gnomi

Invisible

unread,
Dec 1, 2020, 11:03:18 AM12/1/20
to ldmud...@googlegroups.com
On 01.12.20 14:56, Gnomi wrote:
>
> This was in the original proposal and there were no voices against it, yet:

I dont have anything against it, I'd rather wish it would work for
objects as well ;-)

With a serialisation that can pack multiple program variables into one
line (instead of one line per variable) this should be no problem. And
then IMHO it should work the same for both objects and LWOs.

In addition to the v_item example I can think of many applications of a
save/restore_lwobject. Basically everytime an object needs to
save/restore a datastructure it could be implemented very elegantly via
an LWO that can save/restore itself from disk (as opposed to doing the
file-IO outside of the LWO). This would also allow for easily replacing
disk storage with e.g. a database later on (just use a different LWO
that implements its save/restore() differently).



Tangent:

btw. that gives me another idea: how about directly passing values to
the create() function ("constructor") to streamline this even further?
This should only be a matter of passing additional (varargs) arguments
from load_object/clone_object/new_lwobject on to the driver hooks, e.g.

object named_human = clone_object("/npc/human", "Klaus", "male");
or
lwobject userdata = new_lwobject("/lwo/userdata", username);


cya

  Invis

Gnomi

unread,
Dec 1, 2020, 12:03:49 PM12/1/20
to ldmud...@googlegroups.com
Hi Invisible,

Invisible wrote:
> On 01.12.20 14:56, Gnomi wrote:
> > This was in the original proposal and there were no voices against it, yet:
>
> I dont have anything against it, I'd rather wish it would work for objects
> as well ;-)
>
> With a serialisation that can pack multiple program variables into one line
> (instead of one line per variable) this should be no problem. And then IMHO
> it should work the same for both objects and LWOs.

So an inline save format should be used for save_value(ob)? What about
an array of objects? What about the object being part of another object's
variable?

Do you really want whole objects be saved in another object's savefile?
If not why should any of the above examples be treated differently?

But that is not even the real question here. That is: How should such a
save string be restored?

> Tangent:
>
> btw. that gives me another idea: how about directly passing values to the
> create() function ("constructor") to streamline this even further? This
> should only be a matter of passing additional (varargs) arguments from
> load_object/clone_object/new_lwobject on to the driver hooks, e.g.
>
> object named_human = clone_object("/npc/human", "Klaus", "male");
> or
> lwobject userdata = new_lwobject("/lwo/userdata", username);

I already implemented this a year ago for objects here:
https://github.com/amotzkau/ldmud/tree/clone_object

I haven't merged it into the mainline, because there is a smell blemish:
The create() function historically get's a parameter, a zero, so it can
be distinguished from the reset call, which gets a one.

This first argument will be a source of mistakes, because nobody knows
about it. But there might be mudlibs out there depending on it.
So the question is whether to break compatibility and remove that argument.

Regards,
Gnomi

Invisible

unread,
Dec 1, 2020, 1:22:18 PM12/1/20
to ldmud...@googlegroups.com
On 01.12.20 18:03, Gnomi wrote:
>
> So an inline save format should be used for save_value(ob)? What about
> an array of objects? What about the object being part of another object's
> variable?
>
> Do you really want whole objects be saved in another object's savefile?
> If not why should any of the above examples be treated differently?
>
I meant that save_value() could just return a list (in whatever
serialisation-fomat you desire) of all the object's variables in a
single line, instead of the one-per-line format of save_object(). No new
data, just a different "arrangement" of the values, with variables of
type 'object' exactly how they are stored currently ("0" iirc).

Or did you mean, that save_object() currently just uses save_value() for
one variable per line, so returning a non-zero result for
save_value(object) would recurse over all object references as a
side-effect?

Well, it's not that important. Priority for me would be a
save_object()-equivalent for LWOs (in the same one-variable-per-line
format).


> But that is not even the real question here. That is: How should such a
> save string be restored?

same as for 'object' variables in save_object/restore_object, i.e. not
at all.



> I already implemented this a year ago for objects here:
> https://github.com/amotzkau/ldmud/tree/clone_object

Ah, great!

So for LWOs we can expect to have this feature from the start?


> I haven't merged it into the mainline, because there is a smell blemish:
> The create() function historically get's a parameter, a zero, so it can
> be distinguished from the reset call, which gets a one.
>
> This first argument will be a source of mistakes, because nobody knows
> about it. But there might be mudlibs out there depending on it.
> So the question is whether to break compatibility and remove that argument.

As far as I can see this parameter is only relevant for compat-mode - at
least that's how it is documented; so __COMPAT_MODE__ could be used to
decide if the flag needs to be present. Native-mode libs will have a
separate reset() anyways, so I cannot imagine anybody is actively using
this undocumented behavior.

And if someone converted a compat-mode lib to run in native mode they
probably implemented H_CREATE_OB/_CLONE to call reset(), so that would
most likely be the only place where they'd potentially have to change
code (if at all). Am I right to assume that this argument is passed to
H_CREATE_OB/H_CREATE_CLONE/H_RESET in case that people just went the
cheap route and set all of those to (string)"reset"? Well then, changing
this into simple unbound_lambda()s that hardcode the parameter shouldn't
be too complicated (and could just be mentioned in the changelog).

In the worst case: make it a compiletime option for the driver to enable
the flag, if it missing really breaks code.


cya,

  Invis

Gnomi

unread,
Dec 1, 2020, 2:06:49 PM12/1/20
to ldmud...@googlegroups.com
Hi,

Invisible wrote:
> As far as I can see this parameter is only relevant for compat-mode - at
> least that's how it is documented; so __COMPAT_MODE__ could be used to
> decide if the flag needs to be present. Native-mode libs will have a
> separate reset() anyways, so I cannot imagine anybody is actively using this
> undocumented behavior.

Currently this is the behavior for both compat mode and native mode.
It is documented as such in /doc/hook/reset and /doc/hook/create_*.

> And if someone converted a compat-mode lib to run in native mode they
> probably implemented H_CREATE_OB/_CLONE to call reset(), so that would most
> likely be the only place where they'd potentially have to change code (if at
> all). Am I right to assume that this argument is passed to
> H_CREATE_OB/H_CREATE_CLONE/H_RESET in case that people just went the cheap
> route and set all of those to (string)"reset"?

Yes, because that was the behavior since before those driver hooks were
invented. You can see it in the LP-2.4.5 mudlib that comes with the driver,
that all the hooks are set to "reset" to have the same behavior as before.

> Well then, changing this into
> simple unbound_lambda()s that hardcode the parameter shouldn't be too
> complicated (and could just be mentioned in the changelog).

I don't know if somebody does that, but then, when you are checking
previous_object(0) in your reset() function, this will indicate then
your object instead of the creator or 0 (in case of a reset).
So you'll need to check your reset()s.

> In the worst case: make it a compiletime option for the driver to enable the
> flag, if it missing really breaks code.

We want to get rid of compat mode and similar compile time options that
change the behavior, not extend them.

Regards,
Gnomi.

Invisible

unread,
Dec 1, 2020, 6:48:02 PM12/1/20
to ldmud...@googlegroups.com
On 01.12.20 20:06, Gnomi wrote:
> Currently this is the behavior for both compat mode and native mode.
> It is documented as such in /doc/hook/reset and /doc/hook/create_*.

ok, i misinterpreted your "nobody knows about it"...


>
>> Well then, changing this into
>> simple unbound_lambda()s that hardcode the parameter shouldn't be too
>> complicated (and could just be mentioned in the changelog).
> I don't know if somebody does that,
does what? use lambdas in driver_hooks, that eventually call
create()/reset()? Well... we do in RoleMUD :-)

> but then, when you are checking
> previous_object(0) in your reset() function, this will indicate then
> your object instead of the creator or 0 (in case of a reset).
> So you'll need to check your reset()s.

ok, but now we're down a rabbithole of exceedingly unlikely things,
aren't we?


>> In the worst case: make it a compiletime option for the driver to enable the
>> flag, if it missing really breaks code.
> We want to get rid of compat mode and similar compile time options that
> change the behavior, not extend them.

I wouldn't strictly see it as "compat-mode-related", but simply a new
feature that might break backward compatibility - so it can be disabled
if required. If you want to get rid of compat-mode you can do that
regardless of this option.

cya

  Invis

Invisible

unread,
Dec 2, 2020, 6:49:33 AM12/2/20
to ldmud...@googlegroups.com
On 02.12.20 00:47, Invisible wrote:
>
>>   but then, when you are checking
>> previous_object(0) in your reset() function, this will indicate then
>> your object instead of the creator or 0 (in case of a reset).
>> So you'll need to check your reset()s.
>
Addendum:

additional arguments to clone_object don't affect reset(), so H_RESET
can still be set to "reset".


for H_CREATE_CLONE and H_CREATE_OB, something like

set_driver_hook(H_CREATE_CLONE,
    unbound_lambda( ({ 'ob }),
        ({ #',,
            ({ #'funcall, ({ #'symbol_function, "reset", 'ob }), 0 })
        }) ) );

should emulate the current behavior regardless of any additional
arguments, without changing previous_object(0) (since the lambda is
bound to the current object before execution; we just get two extra
entries of the same object on the caller_stack).


cu

  Invis


P.S.: did I just find a way around unbound_lambda not accepting lfuns?

Gnomi

unread,
Dec 2, 2020, 7:00:15 AM12/2/20
to ldmud...@googlegroups.com
Hi Invisible,

I didn't thought about symbol_function, but that solves the problem.
Either you had the correct previous_object() or you could call static
functions, but not both. With symbol_function this is possible:

set_driver_hook(H_CREATE_OB,
unbound_lambda(({'ob}),
({
#',,
({#'=, 'caller, ({#'this_object}) }),
({#'set_this_object, 'ob}),
({#'=, 'fun, ({#'symbol_function, "create", 'ob}) }),
({#'set_this_object, 'caller}),
({#'funcall, 'fun, 0})
})));

Hopefully only a few mudlibs really need that...

Regards,
Gnomi.

Invisible

unread,
Dec 2, 2020, 7:33:53 AM12/2/20
to ldmud...@googlegroups.com
On 02.12.20 13:00, Gnomi wrote:
> Hi Invisible,
>
> I didn't thought about symbol_function, but that solves the problem.
> Either you had the correct previous_object() or you could call static
> functions, but not both. With symbol_function this is possible:
>
> set_driver_hook(H_CREATE_OB,
> unbound_lambda(({'ob}),
> ({
> #',,
> ({#'=, 'caller, ({#'this_object}) }),
> ({#'set_this_object, 'ob}),
> ({#'=, 'fun, ({#'symbol_function, "create", 'ob}) }),

or rather "reset", if we want to emulate the behavior of
set_driver_hook(..., "reset") for compat-mode style object initialisation.

I think if you mention this solution in the release notes and/or the
appropriate manpages it should be enough.


And if someone wants to modify the behaviour of native-mode create()
with the new feature of additional arguments, I guess they'd simply use
#'apply instead of #'funcall:

set_driver_hook(H_CREATE_CLONE,
unbound_lambda(({'ob, 'args}),
({
#',,
({#'=, 'caller, ({#'this_object}) }),
({#'set_this_object, 'ob}),
({#'=, 'fun, ({#'symbol_function, "create", 'ob}) }),
({#'set_this_object, 'caller}),
({#'apply, 'fun, 'args}),
... whatever post-initialisation needs to be done ...
 })));

correct?

regards,

  Invis

Gnomi

unread,
Dec 2, 2020, 9:23:07 AM12/2/20
to ldmud...@googlegroups.com
Hi,

Invisible wrote:
> And if someone wants to modify the behaviour of native-mode create() with
> the new feature of additional arguments, I guess they'd simply use #'apply
> instead of #'funcall:
>
> set_driver_hook(H_CREATE_CLONE,
> unbound_lambda(({'ob, 'args}),
> ({
> #',,
> ({#'=, 'caller, ({#'this_object}) }),
> ({#'set_this_object, 'ob}),
> ({#'=, 'fun, ({#'symbol_function, "create", 'ob}) }),
> ({#'set_this_object, 'caller}),
> ({#'apply, 'fun, 'args}),
> ... whatever post-initialisation needs to be done ...
> 爙)));
>
> correct?

But then you're limiting the number of additional arguments to one,
(in your example you're expecting one array there), because lambdas
currently can't have a variable number of arguments.

Regards,
Gnomi

Invisible

unread,
Dec 2, 2020, 9:44:54 AM12/2/20
to ldmud...@googlegroups.com
On 02.12.20 15:23, Gnomi wrote:
>
>> ({#'apply, 'fun, 'args}),
>> ... whatever post-initialisation needs to be done ...
>> 爙)));
>>
>> correct?
> But then you're limiting the number of additional arguments to one,
> (in your example you're expecting one array there), because lambdas
> currently can't have a variable number of arguments.


hmm, should I just declare an arbitrary number of arguments then?

unbound_lambda(({'ob, 'arg1, 'arg2, 'arg3, 'arg4, 'arg5, 'arg6}),
({
...
({#'funcall, 'fun, 'arg1, 'arg2, 'arg3, 'arg4, 'arg5, 'arg6}),
...


Or could you call the driver hook with the arguments as array if it is set to a lambda, so apply() could be used to flatten it? Presumably there is a special treatment for lambdas anyways, since they have to be bound to the object before calling.

regards,
Invis

Gnomi

unread,
Dec 2, 2020, 10:08:47 AM12/2/20
to ldmud...@googlegroups.com
Hi,

Invisible wrote:
> On 02.12.20 15:23, Gnomi wrote:
> >
> > > ({#'apply, 'fun, 'args}),
> > > ... whatever post-initialisation needs to be done ...
> > > 爙)));
> > >
> > > correct?
> > But then you're limiting the number of additional arguments to one,
> > (in your example you're expecting one array there), because lambdas
> > currently can't have a variable number of arguments.
>
>
> hmm, should I just declare an arbitrary number of arguments then?
>
> unbound_lambda(({'ob, 'arg1, 'arg2, 'arg3, 'arg4, 'arg5, 'arg6}),
> ({
> ...
> ({#'funcall, 'fun, 'arg1, 'arg2, 'arg3, 'arg4, 'arg5, 'arg6}),
> ...

That's one solution.

> Or could you call the driver hook with the arguments as array if it is set
> to a lambda, so apply() could be used to flatten it? Presumably there is
> a special treatment for lambdas anyways, since they have to be bound to
> the object before calling.

I don't like such magic behavior. What if lambdas learn to have a variable
number of arguments, then it's hard to change this behavior, even though it
would be cleaner then. What if inline closures would be allowed as a driver
hook, should they be treated differently? Also there is another use case,
the additional arguments could be intended for the driver hook itself to
modify its behavior and not for the create() lfun, then packing them into
an array is annoying.

Regards,
Gnomi

Invisible

unread,
Dec 2, 2020, 11:01:12 AM12/2/20
to ldmud...@googlegroups.com
On 02.12.20 16:08, Gnomi wrote:
>
> I don't like such magic behavior. What if lambdas learn to have a variable
> number of arguments, then it's hard to change this behavior, even though it
> would be cleaner then. What if inline closures would be allowed as a driver
> hook, should they be treated differently? Also there is another use case,
> the additional arguments could be intended for the driver hook itself to
> modify its behavior and not for the create() lfun, then packing them into
> an array is annoying.

true...

And if I really want to avoid declaring a number of args in the lamdba I
guess I can always implement a simul_efun::clone_object(string|object
ob, varargs mixed* args) that packs them all into an array, suitable for
my implementation of the hook. Or just make it mandatory to only pass a
single array or mapping.

cu

  Invis

Malcolm Tester

unread,
Jan 4, 2021, 5:08:10 PM1/4/21
to LDMud Talk
I was traveling and missed this discussion.... but my two cents on the naming  and a couple things:
* new_lwobject() seems most appropriate.  Any other functions that need to be different than the original, should use the same *_lw* scheme.
* For this type of operation, there should not be a difference between a blueprint and a clone.  That just seems unnecessary.

Mostly though, is the work of implementing it, worth it?  The idea originated 20 years ago when resources were a premium, and nowadays, aren't.  Nowadays, if I want a lightweight object to deal with network, external, etc, I can just create an object without inheriting anything, and make it a daemon that's always loaded.  I'm sure we all have similar objects in our libs already, master.c excluded.  Or would these objects be exempt from errors like 'too long evaluation' and 'too deep recursion' (i.e. max_eval_cost)?  I sometimes run into that, but there's usually a way around it.

Malc@Infinity

Travis Everett

unread,
Jan 4, 2021, 6:10:46 PM1/4/21
to ldmud...@googlegroups.com
Malcolm,

I think the main (potential) value here is less about one-off daemon objects (though it would save some resources there), and more about saving the overhead in cases where you might want to create many small objects in the process of an execution (parsing XML into a tree of objects).

That said, I am a little unsure myself about whether something like the Python integration undermines the utility of this or not...

T

--
You received this message because you are subscribed to the Google Groups "LDMud Talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ldmud-talk+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ldmud-talk/220e7f52-0773-4eb1-810a-89fb6c3205a4n%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages