ArcEmu: [lua] Table-oriented Scripting - ArcEmu

Jump to content

Toggle shoutbox Lastest Announcements

dfighter  : (07 December 2014 - 12:06 PM) Arcemu is in hibernation mode, please read http://arcemu.org/fo...showtopic=26903
dfighter  : (01 January 2013 - 05:56 PM) Arcemu wishes you all a happy new year!
Hasbro  : (12 September 2012 - 10:01 AM) Please excuse our outage from the web! Our web host had a major malfunction!
dfighter  : (01 September 2012 - 04:05 PM) Since the spam bots just don't want to stop, I've enabled admin verification when registering.
dfighter  : (23 January 2012 - 09:56 PM) Please note that from now on you will need to confirm your email on the wiki in order to edit it!
Hasbro  : (31 December 2011 - 12:50 PM) Happy New Years all!
Navid  : (26 December 2011 - 04:09 AM) Merry Christmas !!!!!! Happy holidays all :)
WAmadeus  : (24 December 2011 - 03:54 PM) Merry Christmas to all!
dfighter  : (24 December 2011 - 11:05 AM) The Arcemu team wishes y'all a Merry Christmukkah!
Hasbro  : (05 October 2011 - 12:53 PM) Looking for web designers for upcoming web related project. If you're interested in designing user interfaces contact me
dfighter  : (02 September 2011 - 03:47 PM) So who here wants vehicles in Arcemu? :P http://arcemu.org/fo...showtopic=25440
Hasbro  : (14 August 2011 - 03:25 PM) Join us on irc, grab an irc client and connect to irc.freenode.net join channel #arcemu /server irc.freenode.net:6667 /join #arcemu
jackpoz  : (03 August 2011 - 05:33 AM) to all Lua Engine (old one) users: please check http://arcemu.org/fo...showtopic=25274
Hasbro  : (20 May 2011 - 05:27 PM) Looking for people experienced with CMake configuration and setup! Contact me asap
Hasbro  : (15 May 2011 - 05:03 PM) ArcEmu is recruiting C++ programmers, contact Hasbro if interested.
paroxysm  : (03 May 2011 - 06:26 PM) Updated luabridge gossip example to describe the whole gossip creation process rather than just how to create menu. Gossip tutorial
paroxysm  : (23 April 2011 - 11:35 AM) Lua writers can refer to the Luabridge Tutorials section in the Wiki to learn how to write gossip code correctly.
Hasbro  : (20 April 2011 - 05:22 PM) Thank you for your continuous contribution of bug reports, we are working on them.
Hasbro  : (17 April 2011 - 03:20 AM) Please consider donating to support our bills. Donations can be sent using PayPal to donations@arcemu.org - Thank you for your support.
paroxysm  : (10 April 2011 - 12:43 AM) Refer to the Luabridge Tutorials section in the Wiki to learn the new syntax of luabridge.
Resize Shouts Area

Page 1 of 1
  • You cannot start a new topic
  • You cannot reply to this topic

[lua] Table-oriented Scripting The "new standards"

#1 User is offline   hypersniper 

  • Advanced Member
  • Group: Retired
  • Posts: 227
  • Joined: 21-November 08
  • Gender:Male

Posted 17 December 2009 - 03:35 AM

Welcome to the new scripting standards announcement
Hey. Today I'm going to teach you how to script bosses properly. "But I thought I was already scripting ber l33t bosses!?" I hear you say? Well, that's true, but most of us know what the current Lua scripting style didn't cut it in many situations. Things like Violet Hold were impossible, with variables being passed around locally. Also, with everything on the global level, performance was impacted and it just didn't look good. I'll further explain this now.

Read this please, it becomes pretty important later.

The problem
1. Conflicting variables. When you have more than one copy of a monster using the same script, variables usually collide.
Example
local player --please never do this from now on.

function NPC_OnSpawn(pUnit, event)
   player = pUnit:GetRandomPlayer(0)
   pUnit:RegisterEvent("NPC_cast_attack", 10000, 1)
end

function NPC_cast_attack(pUnit, event)
   pUnit:CastSpellOnTarget(1234, player)
end

RegisterUnitEvent(1337, 18, "NPC_OnSpawn")
Let's consider that script. So npc #1337 spawns, and chooses a player, and stores it in the script-wide variable player. BUT, what if another npc of #1337 was spawned after the first one? Well, since that npc also uses the same script, player would be changed to whatever the second #1337 picked, and so the first #1337 would have the wrong player. Noes.

2. Everything has to be stored on the global table. What do I mean by this? Well, when you create a variable, ANY variable, Lua stores it in a table. Yep. A Table. And this table is named "_G". So, when you are in fact creating a variable, you are just assigning another key in the _G table.
my_var = 5
_G["my_var"] = 5
Both of those do the same thing, just the top one is the one we use all the time.
Now, in our current Lua practices, EVERYTHING gets thrown onto the _G table. Including variables and functions. So, you can imagine that this _G table gets very cluttered by users' functions and variables, which affects Lua engine performance.

3. Not only does it affect performance, but it's bad programming practice, and we don't want that.

The solution
Well, to solve this, we need to get everything off the _G table. And to do that, we create our own new tables to hold our functions and variables. This is where Functions in Tables comes in.
ILLIDAN = {} --when using this feature make sure you don't "local" these.
function ILLIDAN.OnCombat(pUnit, event, pAttacker) --we create a new function within the ILLIDAN table
   pUnit:SendChatMessage(12, 0, "Rawr!")
end

RegisterUnitEvent(24417, 18, "ILLIDAN.OnCombat")
So we've taken this entire script off of the _G table, much more efficient, modular and win. We can also store script-based variables in our own tables, using unique keys to keep everything private and avoid those collisions I described earlier.
ILLIDAN = {}
function ILLIDAN.OnCombat(pUnit, event, pAttacker)
   pUnit:SendChatMessage(12, 0, "Rawr!")
   local sUnit = tostring(pUnit) --locals within functions are fine, i'm using it here for convenience
   ILLIDAN[sUnit] = {}
   ILLIDAN[sUnit].initialAttacker = pAttacker
end

RegisterUnitEvent(24417, 18, "ILLIDAN.OnCombat")
Yep, that's how you avoid the variables colliding. Let me tell you what actually happens here.
1. <in jargon> We assign the "ILLIDAN" table with a key sUnit, and within that, we assign the "initialAttacker" variable to pAttacker.
2. So, we use sUnit as a key because that is unique. sUnit is the string form of pUnit, and we no there are no two same pUnits. We turn it into a string because, in simple terms, we don't like using a weird object like pUnit as a key.
3. Then we use our dot notation to create our set of variables, which looks better, is cleaner, and isn't buggy.

If you use variables in this way it's good practice to clear out the table when you're done with it, for example when you despawn the creature or he dies.
function ILLIDAN.OnDeath(pUnit, event, killer)
   pUnit:SendChatMessage(12, 0, "I am slain by "..killer)
   local sUnit = tostring(pUnit)
   ILLIDAN[sUnit] = nil
end


Instances
You can script instances in a very similar new way, but it should be done differently. Because creatures often need to share variables in instances, instead of using pUnit as a key for privacy, we use the Instance ID. An instance ID is a unique number automatically generated when a new instance is created. They use it on retail to track what instance groups you're saved to. You can get an instance id by doing pUnit:GetInstanceID().
Example, if our illidan wanted to have a shared variable in an instance:
BLACK_TEMPLE = {}
BLACK_TEMPLE.ILLIDAN = {}
BLACK_TEMPLE.MOTHER = {}
function BLACK_TEMPLE.ILLIDAN.OnCombat(pUnit, event, pAttacker)
   local id = pUnit:GetInstanceID()
   pUnit:SendChatMessage(12, 0, "Rawr!")
   if (BLACK_TEMPLE[id].MOTHER.isDead == true) then
      pUnit:SendChatMessage(12, 0, "You killed my mother! This is revenge!")
   end
end

function BLACK_TEMPLE.MOTHER.OnDied(pUnit, event, pKiller)
   local id = pUnit:GetInstanceID()
   BLACK_TEMPLE[id].MOTHER.isDead = true
end

RegisterUnitEvent(24417, 18, "BLACK_TEMPLE.ILLIDAN.OnCombat")
RegisterUnitEvent(24654, 4, "BLACK_TEMPLE.MOTHER.OnDied")

The end
While I have striven to make this guide as clear, concise and understandable as possible, there may be something that just isn't. If that happens, please post :(
Seriously, post here or MSN me if you're lost, I'm happy to help.
And I don't want to see any of the old kind of scripts anymore. These are new times!
People who release a script in this fashion will get incredible congrats from me. Especially the first few brave fellows.
Please don't PM me asking to fix, correct or look-over your scripts. Please post a new thread in the Lua Scripting section so others can learn and help.
0

#2 User is offline   hypersniper 

  • Advanced Member
  • Group: Retired
  • Posts: 227
  • Joined: 21-November 08
  • Gender:Male

Posted 17 December 2009 - 06:51 PM

Did a fail here, since pUnit is the userdata memory containing the Unit, it can change between pushes, therefore making it not unique per Unit. We should use tostring(pUnit) as keys instead since that bases it's value of the Unit within the userdata. Will update guide soon.

---Guide updated---
Please don't PM me asking to fix, correct or look-over your scripts. Please post a new thread in the Lua Scripting section so others can learn and help.
0

#3 User is offline   gambini 

  • Member
  • Pip
  • Group: Members
  • Posts: 69
  • Joined: 24-October 08

Posted 20 December 2009 - 12:12 PM

This!




Okay, I'll say more :). This is what I've been wondering about how to do. I knew it had to do with tables, but I could never get a solid idea of what needed to be done.

Question time: Let's say I would like to use words to represent my spells instead of the spellid. As of now, I've always had a billion lines of "local <spell name> = <spell id>" at the top of the script. Since those are constants, should I just keep them as they are, or should I make a new table for efficiency purposes? The reason I ask is that you said every variable and function is loaded into the table "_G", so would it be better for them to be in a table that isn't _G?

Which brings me on to another train of thought. Why aren't these new tables stored in _G? It sounded like _G is the places that variables go and hang out until they are called upon. I guess I am asking a little bit more info on what _G is/holds -- which is kind of a different subject, but my curiosity is killing me.

Hyper, as always, you da man.
0

#4 User is offline   paroxysm 

  • Chatty Cathy
  • Group: Retired
  • Posts: 320
  • Joined: 25-June 08
  • Gender:Male
  • Server OS:Windows

Posted 20 December 2009 - 02:18 PM

Everything that is declared is stored on _G unless it is specifically stored in another table. All global tables go on _G but the idea is that, you have a single table in _G, and that table contains all the variables and functions and even more tables that you need for that particular subject. You end up with only 1 entry in the _G table which continues to add up the more scripts follow the same convention.
Ideally the generic way to go about writing instance scripts is having a global table such as INSTANCE_ZULAMAN or INSTANCE_BLACKTEMPLE, and everything related to that instace would go in their respective tables, but bear in mind that you can also clutter up things in any table, so encapsulate different encounters into different tables inside that instance table.
0

#5 User is offline   gambini 

  • Member
  • Pip
  • Group: Members
  • Posts: 69
  • Joined: 24-October 08

Posted 20 December 2009 - 04:44 PM

View Postparoxysm, on 20 December 2009 - 02:18 PM, said:

Everything that is declared is stored on _G unless it is specifically stored in another table. All global tables go on _G [...]


That seems contradictory. Kinda messed with my head, and made some of the rest of your post not make sense. Elaborate on what you mean there, if you don't mind.
0

#6 User is offline   paroxysm 

  • Chatty Cathy
  • Group: Retired
  • Posts: 320
  • Joined: 25-June 08
  • Gender:Male
  • Server OS:Windows

Posted 20 December 2009 - 06:40 PM

All functions,numbers(unless constant),strings n such are all stored in the _G table by default unless you either declare them in another table, or use the local keyword inside a function's scope(don't think I can be any clearer about this). The way Lua loads files is that it compiles them and turns them into a function to call. The function, like all others has its scope but its scope doesn't end until Lua shutsdown, so in effect the scope is somewhat global. This would explain 2 files having the same variable that is declared locally don't overwrite each other.
0

#7 User is offline   gambini 

  • Member
  • Pip
  • Group: Members
  • Posts: 69
  • Joined: 24-October 08

Posted 21 December 2009 - 01:45 AM

Ah that makes some sense. I just read the post before as "Everything that you stick into a user made table doesn't go in _G. All of the tables are stored in _G." Which by my thinking, the first sentence is contained in "all tables," and hyper said in his post that we were trying to get everything out of _G. I'm probably reading it incorrectly, but other than that, I pieced together everything else just fine and it made sense.

So for efficiency reasons, it would be a good idea to stick constants in to a table rather than declare them all as locals outside of any function? Not sure how Lua handles tables -- so this is more than likely incorrect -- but if you have the index of a container (table holding the constants), then it would be faster to find the container than search through all of _G for the variable each time one is called? Or am I missing something important?


But where are tables stored? Are they all separate, or is there some table that holds a reference to all the tables? If so, could we have the possibility of table clutter? Or is that a non-issue?
0

#8 User is offline   dfighter 

  • Titles are overrated
  • PipPipPipPipPipPipPipPipPipPip
  • Group: Administrator
  • Posts: 5,189
  • Joined: 14-June 08
  • IRC:dfighter
  • Gender:Male
  • Server OS:Linux

Posted 11 September 2010 - 08:28 PM

Bump for peace and prosperity.
"The demand for free goods is infinite."
0

#9 User is offline   paroxysm 

  • Chatty Cathy
  • Group: Retired
  • Posts: 320
  • Joined: 25-June 08
  • Gender:Male
  • Server OS:Windows

Posted 17 September 2010 - 12:15 AM

many lulz
0

#10 User is offline   Maroot 

  • Member
  • Pip
  • Group: Members
  • Posts: 73
  • Joined: 07-June 08

Posted 15 July 2011 - 01:00 AM

View Postgambini, on 21 December 2009 - 01:45 AM, said:

So for efficiency reasons, it would be a good idea to stick constants in to a table rather than declare them all as locals outside of any function? Not sure how Lua handles tables -- so this is more than likely incorrect -- but if you have the index of a container (table holding the constants), then it would be faster to find the container than search through all of _G for the variable each time one is called? Or am I missing something important?


Old post I know, been awhile since I last visited, this real life crap sure gets in the way, hard to believe the months roll by so darn fast...

Anyway, to comment on this particular quote snippet.
No, it is still more efficient to declare your constant variables as local rather then storing them inside a table stored on the global stack.
The reason is that your script(function) is a container encapsulating all that's inside it. So all your functions in your container have explicit access to all local constants/variables/functions/tables, etc etc.

Placing them in a table on the global stack only increases the number of instructions needed to access those variables, or what have you.
If they are local there is only one instruction needed to utilize the object your are trying to access for example, placing it/them on the global stack in a table adds more instructions, therefore increasing access time. Though with CPU's today it probably won't even make a noticeable difference, but get thousands of those extra instructions going and all the sudden your trying to find a solution to why the engine is struggling on heavy loads, every bit helps.
0

#11 User is offline   paroxysm 

  • Chatty Cathy
  • Group: Retired
  • Posts: 320
  • Joined: 25-June 08
  • Gender:Male
  • Server OS:Windows

Posted 15 July 2011 - 09:44 PM

well said.
0

Share this topic:


Page 1 of 1
  • You cannot start a new topic
  • You cannot reply to this topic

1 User(s) are reading this topic
0 members, 1 guests, 0 anonymous users