Google Groups unterstützt keine neuen Usenet-Beiträge oder ‑Abos mehr. Bisherige Inhalte sind weiterhin sichtbar.

[TADS3 Library] why were arguments replaced with global variables?

7 Aufrufe
Direkt zur ersten ungelesenen Nachricht

Arthur

ungelesen,
15.09.2003, 19:54:1015.09.03
an
I know I'm way too late to be asking about something like this, but
I'm just curious:

In TADS 2, object methods took several arguments, such as actor, dobj
(direct object), iobj (indirect object), or prep (preposition). This
made it very clear which objects were involved with a command, and
kept object scope contained to the relevant method.

In TADS 3, the game author must reference global objects, like gActor,
gDobj, and gIobj, to manipulate these objects, while the methods
themselves (verify, check, and action) contain no arguments at all.

It seems like this is a step backwards, in terms of following OOP
"best practices," since using global variables to reference objects
which hold contextual "state" is generally regarded as a Bad Thing.

Why was this done? If "code brevity" was the goal, wouldn't it have
been cleaner to have a "command" object, which could be passed to each
method, and rely on the command object to hold references to the
actor, preposition, verb, and objects?

For example:
--------------------------
button: Thing 'button' 'button' "It's a button. "
dobjFor(Push)
{
action(command)
{
if (command.actor == gPlayerChar)
"{You/he} hear bells and flashing lights! ";
}
}
;

Mike Roberts

ungelesen,
15.09.2003, 21:09:5015.09.03
an
"Arthur" <arthur_...@hotmail.com> wrote:
> In TADS 2, object methods took several arguments, such as
> actor, dobj (direct object), iobj (indirect object), or prep
> (preposition).
>
> In TADS 3, the game author must reference global objects, like
> gActor, gDobj, and gIobj, to manipulate these objects, while the
> methods themselves (verify, check, and action) contain no
> arguments at all.

Right. This change was motivated by the observation that, in practice,
there's virtually always a command context, and virtually every method
called needs the entire command context. Even methods that don't directly
need the command context need to be able to pass it to routines they call
frequently enough that eventually everything becomes infected by the need
for these arguments. Even though these were arguments in tads 2, they've
come to take on more the nature of global variables, in that you can get at
them with parserGetObj().

> It seems like this is a step backwards, in terms of following OOP
> "best practices," since using global variables to reference objects
> which hold contextual "state" is generally regarded as a Bad Thing.

My experience has been that Things that are generally regarded as Bad Things
can sometimes be Good Things, and avoiding them dogmatically because they're
usually thought to be Bad Things is a Bad Thing. I think this is one of
those cases where the specific advantages outweigh the general
disadvantages.

The primary disadvantage of using global variables to pass information from
one routine to another is that it creates a protocol for calling routines,
and that protocol isn't embodied syntactically in the invocation of the
routines: every caller of routine X has to know that the argument have to be
loaded into global variables G and H before calling the routine, and if
there's any chance that those callers themselves have been recursively
called from X, they have to know to save the old values somewhere before
invoking X and then restore the old values when X returns. For most
applications, all of this is obviously handled much more easily and cleanly
with function parameters, so modern structured programming practices (never
mind OOP) tell us to use parameters rather than globals for function calls.

In this particular case, though, we have a situation where virtually every
routine in the system has the same parameter environment, and one in which
recursion only occurs at certain bottlenecks. These two factors made it
worthwhile, in my judgment, to factor out that common environment into
globals.

> Why was this done? If "code brevity" was the goal, wouldn't it
> have been cleaner to have a "command" object, which could be
> passed to each method, and rely on the command object to hold
> references to the actor, preposition, verb, and objects?

There is a command object; it's really the main global. Most of the other
apparent globals are really looking into that command object. Passing it
around would still have required a lot of extra typing (in game code) that
would have been essentially superfluous because it would virtually always
have been boilerplate.

In practice, it seems to work out well.

--Mike
mjr underscore at hotmail dot com

Steve Mading

ungelesen,
16.09.2003, 18:08:3216.09.03
an
Mike Roberts <mjrUND...@hotmail.com> wrote:

It also makes recursion uglier when you can't "layer" one version of the
variable on top of the other in the stack.

Arthur Milliken

ungelesen,
25.09.2003, 02:01:4825.09.03
an

I see your point, and in practice, it makes pretty "clean" (brief) code,
at least when I walk through the examples in the "Getting Started" Guide.

What made me object to it was this: the thought occurred to me to use
the TADS3 engine to try to create a *multiuser* IF adventure. In a
multiuser IF adventure, however, it would be impossible to separate the
command context for different users from each other, since they all
share the same set of global variables.

Even in a single player context, what if the user's behavior triggers an
NPC to execute his own command? Wouldn't the NPC's command context get
confused with the player's command context?

John Francis

ungelesen,
25.09.2003, 03:19:0225.09.03
an
In article <gxvcb.663$u35.14...@newssvr13.news.prodigy.com>,

Arthur Milliken <arthur_...@hotmail.com> wrote:
>>
>> In this particular case, though, we have a situation where virtually every
>> routine in the system has the same parameter environment, and one in which
>> recursion only occurs at certain bottlenecks. These two factors made it
>> worthwhile, in my judgment, to factor out that common environment into
>> globals.
>>
>I see your point, and in practice, it makes pretty "clean" (brief) code,
>at least when I walk through the examples in the "Getting Started" Guide.
>
>What made me object to it was this: the thought occurred to me to use
>the TADS3 engine to try to create a *multiuser* IF adventure. In a
>multiuser IF adventure, however, it would be impossible to separate the
>command context for different users from each other, since they all
>share the same set of global variables.

Perhaps a slightly better approach would be to have a globally-accessible
object which encapsulates the command context, and to make the routines
be methods of this object rather than dimple global routines.

That would let you have different contexts for different users, if needed.

--
Hello. My name is Darth Vader. I am your Father. Prepare to die.

Mike Roberts

ungelesen,
25.09.2003, 14:22:1325.09.03
an
"Arthur Milliken" <arthur_...@hotmail.com> wrote:
> What made me object to [adv3's use of global variables for the
> command context] was this: the thought occurred to me to use

> the TADS3 engine to try to create a *multiuser* IF adventure.
> In a multiuser IF adventure, however, it would be impossible to
> separate the command context for different users from each
> other, since they all share the same set of global variables.
>
> Even in a single player context, what if the user's behavior triggers
> an NPC to execute his own command? Wouldn't the NPC's
> command context get confused with the player's command
> context?

You're right that the multi-user and recursive-NPC cases are pretty much the
same. But the recursive-NPC case actually isn't a problem, so the
multi-user case shouldn't be either. (What would make the multi-user case
difficult is the lack of any socket support in the VM at present - but
that's not a fundamental limit, since the VM has an extensibility
architecture that would make it relatively easy and clean to add networking
support in the future.)

Globals don't make recursion impossible; they just make it inconvenient. A
recursive caller has to save all of the globals somewhere, then set the
globals to their new values, then make the recursive call, and finally
restore the globals to their old values. The best "somewhere" is the stack,
since it will naturally keep everything straight if the recursive call
recurses again, and that one recurses again, etc.; and storing things on the
stack is just a matter of putting them in local variables. So the pattern
for recursive invocation of a routine that takes its parameters via globals
amounts to

local oldA = a, oldB = b;
a = newValueOfA;
b = newValueOfB;
try
{
func();
}
finally
{
a = oldA;
b = oldB;
}

That's obviously less convenient than passing the parameters on the stack in
the first place, but functionally it's the same. Fortunately, this isn't
code that a typical game author would ever have to write, because the
library provides routines that do this for you in all of the contexts where
you're likely to need it - which is what I was getting at in my previous
note when I said that there are a relatively small number of bottlenecks
where a game tends to need recursion of the sort that affects the command
context. For the specific case you raise, of invoking a command on an NPC
recursively, there's a function you can call that does all of this
automatically - the game author merely writes something like

nestedActorAction(bob, Open, ironDoor);

For the multi-user case, the situation is essentially the same. It might
clear up some confusion to note that the VM doesn't have any multi-threading
support, so it's not as though a multi-user game would have separate threads
for the different users running concurrently; everything would be
single-threaded, so processing of a given user's input would happen in a
straightforward recursive or co-routine context that would allow the same
idiom as above. If multi-threading were a factor, my perspective on globals
would be completely different.

In any case, I'm not sure the library would be all that great for multi-user
gaming anyway, for numerous other reasons. I haven't given multi-user
gaming a lot of thought, but it seems to me to be a rather different beast,
even when considered several design levels up from these sorts of
implementation details. The adv3 library is tailored to a pretty specific
kind of game, and I really doubt it would translate well to a multi-user
set-up.

OKB (not okblacke)

ungelesen,
25.09.2003, 14:30:3825.09.03
an
John Francis wrote:

>>What made me object to it was this: the thought occurred to me to
>>use the TADS3 engine to try to create a *multiuser* IF adventure.
>>In a multiuser IF adventure, however, it would be impossible to
>>separate the command context for different users from each other,
>>since they all share the same set of global variables.
>
> Perhaps a slightly better approach would be to have a
> globally-accessible object which encapsulates the command context,
> and to make the routines be methods of this object rather than
> dimple global routines.
>
> That would let you have different contexts for different users, if
> needed.

As MJR mentioned earlier in the thread, this is actually what the
ADV3 library does. gActor isn't really a global variable, it's a
macro -- a convenient shorthand for libGlobal.curActor. libGlobal is
the "context object" you're talking about. There is a routine called
withActionEnv which executes some specified code with a specified
context. This is how NPC actions are handled: the routine saves the
"enclosing" action context, runs the nested action, then restores the
old context on the way out. This works great in my experience -- it
pretty much hides the hairy innards of the mechanism and lets you run
actions easily.

There is still the assumption that there will only be one player
character at any given time, but this is a fairly fundamental property
of IF as it exists today. I think the library could actually be
extended to multiple players without too much hacking -- you could
simply map all the inputs into a single "dummy" player character,
along with some meta-information about which player is actually doing
the action. Then this dummy object would pass control on to the
appropriate "real" player character accordingly.

--
--OKB (not okblacke)
"Do not follow where the path may lead. Go, instead, where there is
no path, and leave a trail."
--author unknown

Arthur Milliken

ungelesen,
28.09.2003, 16:32:4228.09.03
an
Mike Roberts wrote:

I see. I assume it's also possible to call nestedActorAction with
indirect objects as well?


>
> For the multi-user case, the situation is essentially the same. It might
> clear up some confusion to note that the VM doesn't have any multi-threading
> support, so it's not as though a multi-user game would have separate threads
> for the different users running concurrently; everything would be
> single-threaded, so processing of a given user's input would happen in a
> straightforward recursive or co-routine context that would allow the same
> idiom as above. If multi-threading were a factor, my perspective on globals
> would be completely different.

I was wondering about that. I suppose if there's no multi-threading,
then the problem becomes much more manageable. Thanks for the explanation.

>
> In any case, I'm not sure the library would be all that great for multi-user
> gaming anyway, for numerous other reasons. I haven't given multi-user
> gaming a lot of thought, but it seems to me to be a rather different beast,
> even when considered several design levels up from these sorts of
> implementation details. The adv3 library is tailored to a pretty specific
> kind of game, and I really doubt it would translate well to a multi-user
> set-up.
>

Fair enough. If I want to move into the multiuser realm, I will
consider other engines for that. As it is, I'm very impressed with the
scope and completeness of adv3. Thanks for the great work!

--Arthur

Mike Roberts

ungelesen,
29.09.2003, 00:01:0429.09.03
an
"Arthur Milliken" <arthur_...@hotmail.com> wrote:
> > nestedActorAction(bob, Open, ironDoor);
>
> I see. I assume it's also possible to call nestedActorAction with
> indirect objects as well?

Right - the meaning of the argument list is determined by the individual
action subclass, so you can pass it whatever objects the action requires.

0 neue Nachrichten