HeroEngine Forums
Welcome, Guest. Please login or Register for HeroCloud Account.

Author Topic: [Resolved] Prototypes vs Classes  (Read 3112 times)

toddseiler

  • General Accounts
  • *
  • Posts: 3
    • View Profile
[Resolved] Prototypes vs Classes
« on: Dec 15, 10, 12:28:36 AM »

This is a newb question.
I've read the prototype page on the wiki a few times and something just isn't clicking with me.

What is the purpose of a prototype? Besides being a singleton.

Why couldn't I just instantiate a node from a class? And then clone it (if that's possible).

What is a prototype trying to solve exactly over a class? It seems like a prototype can do everything a class can.

Could you provide examples of when I would use a class vs. a prototype?

Thanks!
-Todd
« Last Edit: Nov 02, 12, 10:17:38 PM by HE-Cooper »
Logged

FI-ScottZ

  • General Accounts
  • *
  • Posts: 1407
    • View Profile
    • Forever Interactive, Inc.
Re: Prototypes vs Classes
« Reply #1 on: Dec 15, 10, 01:52:22 AM »

From what I've read, HeroScript does not have anything for constructors or destructors, so if you create a node from a class, all of the fields have empty values.  A prototype can have values set in it, so when you create a node from a prototype, the node will have those values.  Thus, a prototype effectively acts as a constructor.  There may be other points to them as well, but this is how I have been thinking of them.
Logged
Scott Zarnke
Lead Programmer, Visions of Zosimos
CTO, Forever Interactive, Inc.

HE-CHRISTOPHER

  • HeroEngine
  • *****
  • Posts: 424
    • View Profile
Re: Prototypes vs Classes
« Reply #2 on: Dec 15, 10, 09:01:51 AM »

Scott's response is essentially correct, just a few additional points...

Prototypes:
  • have initial/default values
  • can have behaviors (i.e. you can call a method on a prototype), classes on the other hand can not invoke methods without an instantiation of the class (i.e. you can not call methods on local class variables).
  • are coordinated so that all area servers have exactly identical data for a prototype (note, while it is good an wise to modify prototypes during development you should not write a system that does so in the production game
  • are good for immutable (in a production game) data
  • are part of the Data Object Model (DOM) just like class/field definitions (not the GOM where instantiations live)
  • serve as the base storage mechanism for the spec system

Classes:
  • do not have default values
  • members are initialized in their conceptually equivalent of empty string or zero
  • have no callable behaviors if not instantiated in the GOM


Why one vs the other?

It is common that default values are not needed so instantiating directly from a class is perfectly adequate and marginally faster.  The most common use of prototypes is within the spec system, which does not create new instantiations directly from the prototype...rather the prototype acts as a factory for instances allowing for significantly more complex construction to occur.

Logged
Christopher Larsen
CTO
HeroEngine

FI-ScottZ

  • General Accounts
  • *
  • Posts: 1407
    • View Profile
    • Forever Interactive, Inc.
Re: Prototypes vs Classes
« Reply #3 on: Dec 15, 10, 11:16:53 AM »

A few other things:
  • I understand all System Nodes are prototypes, but then are all prototypes necessarily Systems Nodes?
  • The organizer has tabs to list protoypes, but it doesn't let you see the values set in them or edit them.  Is there a prototype editor, or can that only be done through the CLI?
  • I have been reading about DOM coordination and how it is impractical to add/delete/modify prototypes while the game is being played.  Presumably, the game would have to be taken down for maintenance to do those things.  Would glomming a class onto a prototype also require DOM coordination?

« Last Edit: Dec 15, 10, 12:11:44 PM by ScottZarnke »
Logged
Scott Zarnke
Lead Programmer, Visions of Zosimos
CTO, Forever Interactive, Inc.

HE-CHRISTOPHER

  • HeroEngine
  • *****
  • Posts: 424
    • View Profile
Re: Prototypes vs Classes
« Reply #4 on: Dec 15, 10, 12:27:28 PM »

A few other things:
  • I understand all System Nodes are prototypes, but then are all prototypes necessarily Systems Nodes?
  • The organizer has tabs to list protoypes, but it doesn't let you see the values set in them or edit them.  Is there a prototype editor, or can that only be done through the CLI?
  • I have been reading about DOM coordination and how it is impractical to add/delete/modify prototypes while the game is being played.  Presumably, the game would have to be taken down for maintenance to do those things.  Would glomming a class onto a prototype also require DOM coordination?


  • System nodes are singleton instantiations in the GOM which are instantiated from a prototype of the same name.  HSL has a convenient interface for referencing them "$PROTOTYPENAME" to deal with the fact that their IDs are different in every GOM.  All that is needed to create a system node is using the $ syntax in script, and assuming a prototype with that name exists a singleton will be instantiated in the GOM in which the script was running.
  • There is no generic prototype editor in the C# interface...but if you use the Spec System you are technically editing a prototype... :).  You can modify prototypes through the CLI or HSL.
  • Any modification to a prototype including GLOMming, will result in a DOM Coordination event.

For data structures that need more global in nature and mutable in a production game, a system area(s) in conjunction with arbitrary root nodes comes close to the features of prototypes (though they are loaded asynchronously, you can load non-persistent root node copies in any number of GOMs).  Hero's Journey utilized this basic pattern for their Quest System.

« Last Edit: Dec 15, 10, 12:32:41 PM by HE-CHRISTOPHER »
Logged
Christopher Larsen
CTO
HeroEngine

FI-ScottZ

  • General Accounts
  • *
  • Posts: 1407
    • View Profile
    • Forever Interactive, Inc.
Re: Prototypes vs Classes
« Reply #5 on: Dec 15, 10, 12:38:38 PM »

Thanks, Christopher.  From the wiki, I was thinking that the prototype was serving as the system node since it is a node itself, but I see now that a separate node is created when first referenced in an area.  Makes sense now.
Logged
Scott Zarnke
Lead Programmer, Visions of Zosimos
CTO, Forever Interactive, Inc.

toddseiler

  • General Accounts
  • *
  • Posts: 3
    • View Profile
Re: Prototypes vs Classes
« Reply #6 on: Dec 15, 10, 03:47:27 PM »

Ok thanks!

Just for a little clarification:
So it just seems like a prototype...
1) Can contain data (immutable)
2) Initializes data when a node is instantiated from the prototype
3) Is a factory
4) Also, stating that a prototype is a singleton is not correct because it is a definition, not really an instantiation.

A question for you though:
You mentioned that a method could be called on a prototype. Since methods usually modify the object they're dealing with (the prototype), they would be used to modify the prototype in this case.

1) This would require DOM coordination then right?

I'm not sure why you would want to call methods on a prototype since they would contain the same methods used for your object: say an apple. That's like having your factory/prototype having an "eat()" method. It doesn't really make sense to me. Could you clarify this a little more? What purpose does this have?

Because you would create a class such as "Item".. then create a prototype from that... The prototype has the same methods as the class, correct? Which, in this case, might be a method such as "UseItem()"... Why would you ever call this on a prototype?

Thanks for your help!

-Todd
Logged

HE-CHRISTOPHER

  • HeroEngine
  • *****
  • Posts: 424
    • View Profile
Re: Prototypes vs Classes
« Reply #7 on: Dec 16, 10, 08:34:43 AM »

Hold onto your hats...this on is going to be rather lengthy.




So it just seems like a prototype...
1) Can contain data (immutable)

Yes.

Quote
2) Initializes data when a node is instantiated from the prototype

Yes

Quote
3) Is a factory

Optionally, assuming you implement methods on it to factory an object.  Not to be confused with the external function CreateNodeFromPrototype() which simply creates a mutable instantiation in the GOM that is an identical copy of the prototype.

Quote
4) Also, stating that a prototype is a singleton is not correct because it is a definition, not really an instantiation.

Yup.  Though the script interface allows you to treat them similarly to an instantiation, they really are definitions in the DOM just like class/field definitions.

Quote
A question for you though:
You mentioned that a method could be called on a prototype. Since methods usually modify the object they're dealing with (the prototype), they would be used to modify the prototype in this case.

1) This would require DOM coordination then right?

If the method modifies the prototype, yes it would...read further for why you would have methods that do not modify the prototype.

Quote
I'm not sure why you would want to call methods on a prototype since they would contain the same methods used for your object: say an apple. That's like having your factory/prototype having an "eat()" method. It doesn't really make sense to me. Could you clarify this a little more? What purpose does this have?

The easiest way to explain this is to talk about how one might define and object that has "durability" using the spec system.  Logically, durability tends to have two stats associated with it one immutable "maxDurability" and one mutable "currentDurability".  

A naive implementation of durability might have every item instantiation have fields for both, in fact even really the developers on some extremely large MMOs have made this type of mistake.  This issue being, excessive redundant copies of data that does not change (i.e. immutable data) "maxDurability".  Ignoring for the moment that some game designs might allow modification of maxDurability through magic or whatever.  If you make this mistake, then when there are 10000000 swords in your MMO, each one has memory allocated to store a value that never changes.

The spec system works to solve this problem (among the many it solves) by splitting your data into (conceptually) two logical containers for data.  One container is an instantiation in the GOM for storing mutable data (i.e. currentDurability) and you might call it a "sword".  The other container is a definition (prototype) in the DOM storing immutable data (i.e. maxDurability).  This implies that they are structurally different from each other, and in fact they are...

Assuming you are using a flexible item definition (as opposed to a more rigid hierarchy, greater discussion on this can be found http://hewiki.heroengine.com/wiki/How_to_make_an_Inventory_System), you might create two classes that will be GLOMmed:

class: hasDurability
field: currentDurability
method: ModifyCurrentDurability( amount_of_change as integer )
method: GetCurrentDurability() as Integer
method: GetMaxDurability() as Integer

class: hasDurabilitySpecDecorator
field: maxDurability
method: GetMaxDurability() as Integer

You'll notice that the hasDurability class has a method for getting max durability, but it does not actually have a field storing that value.  The spec's decorator class does have the maxDurability field...so how does this work?  Essentially we've added a level of indirection to things, allowing us to eliminate redundant copies of maxDurability.  Whenever the instantiation's GetMaxDurability() method is called, forwards the call to the specification for the answer.

Code: [Select]
method GetMaxDurability() as Integer
  var spec = me.GetMySpec()
  return spec.GetMaxDurability()
.

Quote
Because you would create a class such as "Item".. then create a prototype from that... The prototype has the same methods as the class, correct? Which, in this case, might be a method such as "UseItem()"... Why would you ever call this on a prototype?

Hopefully from the previous (rather lengthy) answer, its clear that while you could create a prototype "item" and instantiate objects from it using CreateNodeFromPrototype() that is often not the right answer for representation of game data.  Using the spec system, you more typically end up with a prototype "ItemSpec" that has some number of "decorators" GLOMmed onto it that is a factory for a particular "item".  

What does the factory (method) pattern buy us here?  

http://en.wikipedia.org/wiki/Factory_method_pattern

Factories encapsulate the construction of objects that may be extremely complex and allow the factory classes to do whatever is necessary for the construction of a functional object.

Now, as far as UseItem() being called on the prototype...its probably not something called directly but rather again there is a level of indirection involved where UseItem() is called on the instantiation and the instantiation forwards that to the prototype for processing perhaps passing additional information to a different method (UseItemViaSpec( person_activating_item as noderef, item_being_activated as noderef ).  The prototype might have a bunch of decorators that do stuff when an item is "used" but the item itself does not really need to carry around the additional data or classes unless there is mutable data involved.  Those decorators might be things like:

  • UsableItemDecorator
  • ModifyHealthOnUseDecorator
  • HasChargesDecorator

UsableItemDecoratorClassMethods

Code: [Select]
method UseItemViaSpec( character_using_item as noderef, item_being_used as noderef )
  foreach s in QueryPrototypeScriptsWithFunction( me, "OnUseItem" )
     s:OnUseItem( me, character_using_item, item_being_used )
  .
.

ModifyHealthOnUseDecoratorClassMethods

Code: [Select]
shared function OnUseItem( spec as noderef, character_using_item as noderef, item_being_used as noderef )
   modify_health_by as integer = spec.GetHealthModification()
   character_using_item.ModifyHealth( modify_health_by )
.

HasChargesDecorator

Code: [Select]
shared function OnUseItem( spec as noderef, character_using_item as noderef, item_being_used as noderef )
  charges_consumed_on_use as integer = spec.GetChargesConsumedPerUse()
  item_being_used.ModifyCharges( charges_consumed_on_use )
.

Why shared functions in two of the decorators?  

You may recall the Diamond Problem in inheritance, programming languages address the issue in a variety of ways but fundamentally the problem is if this were a method OnUseItem()  it would be ambiguous whether we meant to call HasChargesDecorator.OnUseItem() or ModifyHealthOnUseDecorator.OnUseItem().  

HeroScript addresses the problem by not allowing the Diamond Problem to occur forcing you to resolve ambiguous methods by overriding them explicitly in a child class or changing them.  This is however problematic when you want to call the same signature in multiple GLOMmed classes...and this is what shared functions are used to achieve.  

Because shared functions are explicitly invoked via <script>:<shared function name> there is no ambiguity involved.  We also have convenient functions supporting DOM/GOM reflection on objects to gather a list of references (to the script) for the classes that implement a shared function...allowing us to iterate through them and call each in turn.

Code: [Select]
/ Returns a list of class method scripts from those classes on the NODE that contain the specified shared FUNCTIONNAME.
// This list only includes the deepest children classes of an inheritance structure that implement FUNCTIONNAME.
external function QueryNodeScriptsWithFunction( node as NodeRef, functionName as String ) as List of ScriptRef

// QueryNodeScriptsWithFunction goes through each class on the node and looks for a shared function of
// the given name in the corresponding ClassMethods script.  It follows the same rules as method
// overriding where the parent classes are checked only if the function isn't found.  The number of
// scripts in the List is at most 1 + the number of glommed classes on the node.

// Returns a list of class method scripts from those classes on the prototype NODE that contain the specified shared FUNCTIONNAME.
// This list only includes the deepest children classes of an inheritance structure that implement FUNCTIONNAME.
external function QueryPrototypeScriptsWithFunction( node as NodeRef, functionName as String ) as List of ScriptRef

// Returns a list of class method scripts from those classes on the NODE that contain the specified shared FUNCTIONNAME.
// The list includes all classes of an inheritance structure that implement FUNCTIONNAME, unlike QueryNodeScriptsWithFunction().
external function AllNodeScriptsWithFunction( node as NodeRef, sharedFunc as String ) as List of ScriptRef

// AllNodeScriptsWithFunction goes through each class on the node and looks for a shared function of
// the given name in the corresponding ClassMethods script.  Ancestors are included even when the shared
// function exists in their descendents (difference from QueryNodeScriptsWithFunction).

// Returns a list of class method scripts from those classes on the prototype NODE that contain the specified shared FUNCTIONNAME.
// The list includes all classes of an inheritance structure that implement FUNCTIONNAME, unlike QueryPrototypeScriptsWithFunction().
external function AllPrototypeScriptsWithFunction( node as NodeRef, sharedFunc as String ) as List of ScriptRef
« Last Edit: Dec 16, 10, 03:54:01 PM by HE-CHRISTOPHER »
Logged
Christopher Larsen
CTO
HeroEngine

toddseiler

  • General Accounts
  • *
  • Posts: 3
    • View Profile
Re: Prototypes vs Classes
« Reply #8 on: Dec 17, 10, 02:24:49 AM »

Thank you so much.
The decorator stuff is somewhat new to me so it'll take some soaking in, but I pretty much understand.
Also, I have been looking for a better explanation of shared functions, so thanks, that really helped a lot.

I appreciate all of your effort and help. Things are slowly starting to make more sense.
It looks like I'll have to dig through all of the spec stuff again and try to understand it better.
Logged