Macro Best Practices

Discuss macro implementations, ask for macro help (to share your creations, see User Creations, probably either Campaign Frameworks or Drop-in Resources).

Moderators: dorpond, trevor, Azhrei, giliath, jay, Mr.Ice

User avatar
zEal
Dragon
Posts: 944
Joined: Sun Mar 22, 2009 2:25 am

Re: Macro Best Practices

Post by zEal »

SpaceShot wrote:Now, I point this out here because a best practice was listed that code for input, display, and so on should be separated into smaller macros. I have found that to be absolutely the kiss of death and it makes the whole application crash a few of our player's machines.
Interesting, I've actually had the complete opposite results when it comes to breaking large macros into smaller chunks. However, there is one large problem that could appear when doing this, and I fall victim to it myself at times... and that's the macro coder simply over-looking how much actual macro code is being evaluated on a given call when macros have been broke apart.

Also, if you have a macro that is inefficient to begin with, breaking it into three macros isn't going to automatically give you a more efficient macro... it will give you three inefficient macros. ;)

Craig
Great Wyrm
Posts: 2107
Joined: Sun Jun 22, 2008 7:53 pm
Location: Melbourne, Australia

Re: Macro Best Practices

Post by Craig »

SpaceShot wrote: As I stepped through MapTool code, I guessed (and it was a pure guess) that the culprit was passing a json from macro to macro in some of the calls. Now, in many environments this is great. I can't say for sure if that json is being "passed by reference" or "passed by value" (which is an implementation detail), but I noted when I reduced the data being passed around as much as possible and made chunkier macros, the lag disappeared and the crashing stopped.
I just wanted to clear this up since its been asked a few times. When most people say pass by reference vs pass by value they are asking something different from what they think they are asking, pass by value seems to be a very misunderstood term, there is no speed difference between pass by reference/value (a lot of languages don't even have pass by reference, Java and C for example and when was the last time you heard some one complain C functions take too long to call). BTW I am aware that there are a very few languages/compilers this is not true for but any such language/compiler that pass by value is significantly slower than reference is broken and should be avoided ;)

Now having addressed that... Things are passed by value.
SpaceShot wrote:

So, that is why I am careful to say this is merely my experience.

So, as a professional in the field, I had to agree with the solid programming practice of abstraction, abstraction, abstraction. I just found in practice, I couldn't do it here. And I figured if this was a best practice thread, I should let others benefit from my experience.
Its generally faster to access data in token properties than JSON objects, this is especially the case when you store a JSON object in a token property. Token properties can only hold strings -- if you want other things in MT to work correctly. So when you store a JSON object/array in a token property it gets converted into a string, when you read it back it gets converted from a string to a JSON object (within the macro the JSON object is stored in an internal faster to access format). So in the case where you already know all the field names of your object you are better off storing stuff in token properties -- using the function Wiki: getMatchingProperties() you can even store a lot of dynamically created objects this way. You can even give your token properties a hierarchical type naming convention such as skill.sword.toHit, skill.sword.damage (etc).

You can still abstract everything using token properties (after all these are just storage nothing else), and you can greatly reduce what you are passing to other functions, lookup times in JSON objects etc.

JSON objects are great for passing transitory stuff, but if you are going to read/store it in a token property anyway its faster to read a bunch of values individually from properties in the function there than passing a JSON object to a function.

User avatar
RPTroll
TheBard
Posts: 3159
Joined: Tue Mar 21, 2006 7:26 pm
Location: Austin, Tx
Contact:

Re: Macro Best Practices

Post by RPTroll »

Freakin' GD rewrite. <sigh>
ImageImage ImageImageImageImage
Support RPTools by shopping
Image
Image

Craig
Great Wyrm
Posts: 2107
Joined: Sun Jun 22, 2008 7:53 pm
Location: Melbourne, Australia

Re: Macro Best Practices

Post by Craig »

RPTroll wrote:Freakin' GD rewrite. <sigh>
If what you have works well then there is no need to rewrite anything.
If its slow then you can probably/hopefully get away with only small changes.

User avatar
zEal
Dragon
Posts: 944
Joined: Sun Mar 22, 2009 2:25 am

Re: Macro Best Practices

Post by zEal »

Craig wrote:
RPTroll wrote:Freakin' GD rewrite. <sigh>
If what you have works well then there is no need to rewrite anything.
If its slow then you can probably/hopefully get away with only small changes.
Don't blame Craig, he has been touting token properties as the fastest thing going for a while now. :P



This is by no means directed at you Troll, and I think Craig agrees with me on this, but people need to stop thinking about token properties solely in the context of campaign property types. Use get/setProperty() to store/retrieve any data you want on a token without a need to worry about if the campaign property types are setup to handle it. Heck, do away with campaign property types altogether and redefine get/setProperty() to be trusted, and your players will have no way of cheating. :)

Even if you don't want to go that far, there really is no reason to have token properties that you only intend to have read or modified by macros to be in a property type at all.. well, perhaps if you wanted a dev property type that gave you easy access to check such properties while developing your macros.. I can't think of any other reason you might ever want them in a property type. /shrug

Granted, you will end up with a minuscule amount of XML clutter in tokens if you go and write token properties to them all willy nilly... which is why a removeProperty() or somesuch function to actually delete a token property as opposed to just setting it's value as empty.. if it were possible.. before 1.3final.. would be an excellent addition. >.>

User avatar
RPTroll
TheBard
Posts: 3159
Joined: Tue Mar 21, 2006 7:26 pm
Location: Austin, Tx
Contact:

Re: Macro Best Practices

Post by RPTroll »

I have a few that are slow, like the gear and powers token that store a lot of data via json on the token. I'll probably leave it the way it is in hopes we'll have javascript capability REAL SOON NOW.
ImageImage ImageImageImageImage
Support RPTools by shopping
Image
Image

User avatar
RPTroll
TheBard
Posts: 3159
Joined: Tue Mar 21, 2006 7:26 pm
Location: Austin, Tx
Contact:

Re: Macro Best Practices

Post by RPTroll »

zEal wrote:Don't blame Craig, he has been touting token properties as the fastest thing going for a while now. :P

This is by no means directed at you Troll, and I think Craig agrees with me on this, but people need to stop thinking about token properties solely in the context of campaign property types. Use get/setProperty() to store/retrieve any data you want on a token without a need to worry about if the campaign property types are setup to handle it. Heck, do away with campaign property types altogether and redefine get/setProperty() to be trusted, and your players will have no way of cheating. :)

Even if you don't want to go that far, there really is no reason to have token properties that you only intend to have read or modified by macros to be in a property type at all.. well, perhaps if you wanted a dev property type that gave you easy access to check such properties while developing your macros.. I can't think of any other reason you might ever want them in a property type. /shrug

Granted, you will end up with a minuscule amount of XML clutter in tokens if you go and write token properties to them all willy nilly... which is why a removeProperty() or somesuch function to actually delete a token property as opposed to just setting it's value as empty.. if it were possible.. before 1.3final.. would be an excellent addition. >.>
I'm not mad a Craig. :-) I thank him for a great deal. I don't have the time you other framework guys had to comb the forums. I just assumed a JSON was the way to go on token properties and didn't realize there was a hit from converting back and forth.

Regarding the removeProperty function, I think you'd also want removeMatchingProperty() so you could whack equipment.weapon.longsword.* without multiple calls.
ImageImage ImageImageImageImage
Support RPTools by shopping
Image
Image

SpaceShot
Kobold
Posts: 20
Joined: Mon Jun 16, 2008 8:53 am

Re: Macro Best Practices

Post by SpaceShot »

zEal wrote:Also, if you have a macro that is inefficient to begin with, breaking it into three macros isn't going to automatically give you a more efficient macro... it will give you three inefficient macros. ;)
We're actually talking about two different things.

From an abstraction point, I can see the general advice of breaking tasks down into separate macros to be good advice for the purpose of reusability.

I'm merely saying that when I did that, I had problems.

I don't know why, and I don't know if it is something completely unrelated to macro writing (players bad machines?).
Craig wrote:I just wanted to clear this up since its been asked a few times. When most people say pass by reference vs pass by value they are asking something different from what they think they are asking
Good explanation. I know exactly what it means. In my terms, what it means is that when I was using a framework as it was released, it was really really slow on a few players machines. I surmised the culprit was a json that was very large (I want to say it was 3K) that was being used in nested macro calls. I greatly reduced the size of the data I passed (which did cut MANY features from that very full-featured framework) and I also reduced repeated calls into smaller task-oriented macros because I guessed that a new copy of the argument was being made. Sounds like I was right. Speed issues? Nope... except when you consider that the resource requirements were killing two players' machines (they say these machines are "very old") and made our cross-country weeknight game unplayable.

Sometimes there would be an outright stack overflow and other times I suspect Windows swap operations were going on. I can't say how old or how stuffed with spyware these machines are. When one player says it takes 20 minutes to reboot, that tells me its stuffed with loadware, shovelware, trialware, spyware, even if it's "very old".

I have just been offering actual observations based on how I changed things to make our games run silky smooth.

Good explanation on JSON objects vs token props. It will influence some revisions.



Again, I believe the problem I had was specifically related to a very large json being passed from call to call. Craig's post seems to confirm it. Because I didn't want to spend a lot of time, I consolidated many features into larger calls so my (currently about 150 bytes) is wrapped and unwrapped once and dealt with.

In the future I will probably play with it more. For now, things are going great.



Speaking of Javascript, this is really what I wish I could do. I have even toyed with some pseudocode that looks how I wish I could deal with this stuff. I would love to work on this, but so far all I have done is look into Rhino a little bit.

Craig
Great Wyrm
Posts: 2107
Joined: Sun Jun 22, 2008 7:53 pm
Location: Melbourne, Australia

Re: Macro Best Practices

Post by Craig »

SpaceShot wrote: Again, I believe the problem I had was specifically related to a very large json being passed from call to call. Craig's post seems to confirm it. Because I didn't want to spend a lot of time, I consolidated many features into larger calls so my (currently about 150 bytes) is wrapped and unwrapped once and dealt with.
my post didn't verify this at all. In fact I was pointing out passing larger json objects to functions is generally not going to be a problem. Using those larger json objects within those functions is a different story though.

The most time consuming part is going to be the marshaling/marshaling from token properties.

Once the json object is read from the token properties it is accessed by a handle. So pass by value has 0 overhead vs pass by reference.

User avatar
khabalox
Cave Troll
Posts: 35
Joined: Tue Jul 28, 2009 1:07 pm

Re: Macro Best Practices

Post by khabalox »

Rather than derail bard's getInitiative() problem thread, let me reply to this post here, as it has more to do with Best Practices.
The original post:
http://forums.rptools.net/viewtopic.php ... 35#p117235
biodude wrote: Aside:
I recommend instead defining all your functions on the same Lib:token as the macros they reference, in an onCampaignLoad macro on each Lib:Token. You can reference the macro location using "@this", and then not only is the name of the Lib:Token irrelevant (you can change it and the macros / functions will still work), the lib:token is also "stand-alone", so you can drop it into any campaign and it will work without needing other resources.
But, whatever makes more sense to you is probably the best approach in your case.
That makes sense - I can see the advantages (although I thought I read somewhere that it is recommended to only have one onCampaignLoad() as having multiples can sometimes produce unexpected results). At this point, I've just come up with a handful of pretty basic macros for my PC. I have a few UDFs and initiative related macros which I'm developing for our campaign, and at this point everything (save PC-specific macros) is still on one Lib:token.

Does your way mean having duplicated macros on multiple Lib:tokens?

How do others usually organize their Lib:tokens? Is having multiple onCampaingLoad() functions a problem? Even if they are defining functions of the same name on different tokens?

User avatar
RPTroll
TheBard
Posts: 3159
Joined: Tue Mar 21, 2006 7:26 pm
Location: Austin, Tx
Contact:

Re: Macro Best Practices

Post by RPTroll »

You can run into issues if you copy/paste a lib token. Maptool will run onCampiagnLoad on each so you might not know which macro is being called since each copy of the Lib token will try to define the same function name pointing to its instance of the referenced macro.
ImageImage ImageImageImageImage
Support RPTools by shopping
Image
Image

Beowulfe
Cave Troll
Posts: 73
Joined: Mon Jul 28, 2008 12:38 pm

Re: Macro Best Practices

Post by Beowulfe »

So would this help cut down on lag:
If you have value or string of values to pass from macro to macro, would it be better to use the setProperty, then the called macros would just use the getProperty to get the value? Or would this not make any difference?

User avatar
Azhrei
Site Admin
Posts: 12086
Joined: Mon Jun 12, 2006 1:20 pm
Location: Tampa, FL

Re: Macro Best Practices

Post by Azhrei »

khabalox wrote:Is having multiple onCampaingLoad() functions a problem?
By itself? Not that I know of.
Even if they are defining functions of the same name on different tokens?
Why would you want to do that? For example, a Lib: token that defines a function like findAllTokensWithinDistance() that is passed a token ID and a distance. Would you have another Lib: token with the same function? Why?

I recommend (and this is just general best practice within the industry) to prefix your UDF name with something you think will be unique. In the Java world, that's the domain name of the organization writing the code. That's why the MapTool source code is inside the net.rptools package, for example. Since only one organization can own a given domain name, this is guaranteed unique.

Another option would be to use your own initials. This allows a UDF named fje.someWeirdFunction() and vbe.someWeirdFunction() to co-exist.

<silly_stuff>
Another option -- which isn't currently supported by MapTool and may never be 8)) -- is to define a namespace using a URL, then map the UDF name to that namespace. Folks familiar with XML will recognize this. For example, an XML document may have something like, <xml-stylesheet xmlns:xs="http://www.w3c.org/TR/1999/XSL"> to define a namespace known as xs. Then later the features of that namespace would be referenced using the XML notation <xs:foreach select="//macroProperties"> where the xs: prefix tells the tool where to look for the foreach function. Something like this could be added to MapTool and the URL then becomes the identifier, allowing versioning of the resource and so on. Way overkill for us, though. Especially since JavaScript won't have any support for this technique. ;)
</silly_stuff>

I'm thinking that either of the first two solutions is "good enough" for now. When JS comes along, the naming conventions will be different and I wouldn't be surprised if anonymous objects defined from external sources didn't become the standard. In that situation, the prefixes of the function names don't matter as the user creates an object containing the functions and uses that instead -- their object name effectively becomes the prefix.

User avatar
biodude
Dragon
Posts: 444
Joined: Sun Jun 15, 2008 2:40 pm
Location: Montréal, QC

Re: Macro Best Practices

Post by biodude »

khabalox wrote: That makes sense - I can see the advantages (although I thought I read somewhere that it is recommended to only have one onCampaignLoad() as having multiples can sometimes produce unexpected results).
My understanding is that this is because there's no reliable way to know in which order the onCampaignLoad macros will run when the campaign loads. Thus, you can't write each onCampaignLoad expecting another one to run beforehand. You could force things into a particular order by having a single onCampaignLoad macro that calls macros on other lib:tokens to load their stuff in a particular order, but then they would no longer be "stand-alone".
This shouldn't be a problem if each lib: token is defining different functions in a way that is not dependent on other functions (running them can be dependent, but as long as you are just defining them, that's not a problem).
Note that you can do other things in OnCampaignLoad besides defining functions: this is where I load a default language library into lib:token properties, for example.
khabalox wrote:Does your way mean having duplicated macros on multiple Lib:tokens?
Absolutely Not. See below.
khabalox wrote: How do others usually organize their Lib:tokens? Is having multiple onCampaingLoad() functions a problem? Even if they are defining functions of the same name on different tokens?
I have one Lib:token with generic utility functions (debugging, math, json manipulation, etc.), another with the core system mechanic (d20, Pathfinder, Fudge, etc.), though this could certainly be split up into things like Inventory, Combat, or other largely independent aspects of the system. I'm also thinking of putting together another one for time keeping (effects during inititiative as well as a campaign calendar). So, each lib:token is essentially independent of the others, although they just about all use functions defined on Lib:Utilities, though the onCampaignLoad macros are simple enough that they can run in any order without running into problems.
The point is that by using mostly UDFs (User-defined functions), those lib:tokens can have just about any name, and it won't matter. Having duplicates of such tokens would be a problem, because they are trying to define the same thing multiple times (this can be really problematic with Wiki: oldFunction() ). But, why would you intentionally have duplicate lib:tokens, or define the same function in multiple places?
"The trouble with communicating is believing you have achieved it"
[ d20 StatBlock Importer ] [ Batch Edit Macros ] [ Canned Speech UI ] [ Lib: Math ]

Beowulfe
Cave Troll
Posts: 73
Joined: Mon Jul 28, 2008 12:38 pm

Re: Macro Best Practices

Post by Beowulfe »

Is there any advantages or disadvantages of having multiple lib tokens?

Post Reply

Return to “Macros”