LUA: Passing a local table to a function (for tempTriggers)

Post Reply
Lucky24
Posts: 52
Joined: Sun Sep 12, 2010 1:50 am

LUA: Passing a local table to a function (for tempTriggers)

Post by Lucky24 »

I'm wondering if there is any way to pass a local table value into a trigger/timer, and have it come out on the other side able to use it. I want to be able to pass in the local "self" table, so I can use it within the temp trigger and or timer.

So, someway to pass a local table value into the tempRegexTrigger() like so:
Code: [show] | [select all] lua
local myTable = {"stuff"}
tempRegexTrigger("hello world",[[triggerStuff=myTable]])
So I'd want triggerStuff = {"stuff"}, whereas it would simply throw an error as written above.

And yes, I'm aware I could just use a global variable. But I'm trying to avoid that.

EDIT: I tried the following with no success:
Doens't work because you can't concatenate a table value
Code: [show] | [select all] lua
tempRegexTrigger("hello world",[[triggerStuff=]]..myTable)
I'm asking because I'm writing an object to handle success/fail triggers and timeout commands, and I want to refer back to the calling object/function from within a temp trigger and or timer. Of course, when the trigger is fired, the 'self' table doesn't work, I assume because the trigger code is not being executed from within the original function where 'self' is defined. So when you do something like tempRegexTrigger("hello world",[[self:Start(lua)]]) it doesn't know what 'self' is.

Thus, An actual example of what I'm trying to accomplish (heavily simplified) is as follows:
Code: [show] | [select all] lua
commandWaterfall = commandWaterfall or {}
commandWaterfall.triggers = commandWaterfall.triggers or {}
commandWaterfall.timers = commandWaterfall.timers or {}

-- just make an inheritable object in lua
function commandWaterfall(o)
	o = o or {}
	setmetatable(o, self)
	self.__index = self
	return o
end

-- kill the trigger+timer when either completes
function commandWaterfall:Complete(event,lua)
        if event == "success" then
	        if self.triggers.triggerID then
                        killTrigger(self.triggers.triggerID)
	                self.triggers.triggerID = nil
                end
                if self.timers.timerID then
                        killTimer(self.triggers.timerID)
	                self.timers.timerID = nil
                end
	end
        if event == "timeout" then
	        if self.triggers.triggerID then
                        killTrigger(self.triggers.triggerID)
	                self.triggers.triggerID = nil
                end
                if self.timers.timerID then
	                self.timers.timerID = nil
                end
	end
	return lua
end

-- start the command waterfall
function commandWaterfall:Start(command,successTrigger,successLua,failTrigger,failLua,timeout,timeoutLua)
    self.triggers.successTriggerID = tempRegexTriggger(trigger,[[commandWaterfall.Complete(]]..self..[[,"success",lua)]])
    self.triggers.failTriggerID = tempRegexTriggger(trigger,[[commandWaterfall.Complete(]]..self..[[,"fail",lua)]])
    self.timers.timerID = tempTimer(timeout,[[commandWaterfall.Complete(]]..self..[[,"timeout",lua]])
    expandAlias(command)
end

-- create a new object
eatFood = commandWaterfall:New()

-- start the eatFood command
eatFood:Start("eat food","^You eat some food$",[[send("Yummy!")]],"^You must take the food out of your backpack first$",[[send("get food from my backback");send("eat food")]],5,[[cantEatFood()]])
I had this working without making it into an object, but the inheritable object is nice as you can keep track of the states easily, and have multiple success/fail triggers. I also added a "max retries". It's just simpler within an object than keeping track of all the values in a big list. I suppose I could cheat the inheritance and just call waterfall:Complete() instead of self:Complete(), but that kind of defeats the purpose.

Denarii
Posts: 111
Joined: Thu Dec 03, 2009 10:54 pm

Re: LUA: Passing a local table to a function (for tempTriggers)

Post by Denarii »

The problem is that tempRegexTrigger takes a string and then executes it as a chunk outside of that local environment. So if you do [[triggerStuff=myTable]], then myTable is undefined. So you'll need to serialize that table into a string and concat it in. There are various functions for serializing Lua tables already written, you should be able to find one easily with Google.

Lucky24
Posts: 52
Joined: Sun Sep 12, 2010 1:50 am

Re: LUA: Passing a local table to a function (for tempTriggers)

Post by Lucky24 »

Ah, thank you. "Serialization" is the verb I needed to know :)

For reference, here's the wiki page for lua serialization:

Lua-Users Wiki Table Serialization

Lucky24
Posts: 52
Joined: Sun Sep 12, 2010 1:50 am

Re: LUA: Passing a local table to a function (for tempTriggers)

Post by Lucky24 »

Just a followup on what I actually did to solve this problem for future reference:

I decided *not* to use serialization because I don't want to pass all of the properties of the object forward in time to the tempTrigger; I want to actually pass the object, so that any state changes between when the trigger is created and when it is fired are preserved.

So I created basically a unique key field for each object (in this case the command string that the object initially sends to the server), and then created a table that holds an index/key pairs like so: baseobject.commandList[command]=object. I can then pass only the command string to the trigger and get out the actual object reference by simply specifying the command string in any functions I call from the trigger like so:
Code: [show] | [select all] lua
function baseobject.Complete(command)
        self = baseobject.commandList[command]
end
Here's the actual code I'm using:

NOTE: I'm using a weak reference on the commandList table which I hope will mean that if multiple objects are created with the same command, old ones will be garbage collected when they get overwritten with new ones (as long as those new objects refer to the same table value as the old ones, of course).
Code: [show] | [select all] lua
g.cwf = g.cwf or {}
g.cwf.commandList = g.cwf.commandList or {}
setmetatable(g.cwf.commandList, {__mode = "v"})  --sets weak values - means garbage collect if only referenced here
function g.cwf:New(command)
	if not type(command) == "string" or not command then 	
                scriptLog("\ng.cwf:New() not supplied command string.","error") return false 
        end
	scriptLog("\nCreating new command waterfall for command:"..command,"debug")
	for i,v in pairs(g.cwf.commandList) do
		if i == command then
			g.cwf.commandList[i]=nil
			scriptLog(", removed old commandList object:"..tostring(i))
		end
	end
	o = {}
	setmetatable(o, {__index = self}) --sets inheritance
	g.cwf.commandList[command] = o
	o.command = command
	scriptLog(", added command:"..o.command.." to object:"..tostring(o)..".  ","debug")
	return o
end

-- I then create tempTriggers as follows:
function g.cwf:Start(debugLevel)
	if not debugLevel then debugLevel = self.debugLevel end
	scriptLog("\nStarting command waterfall for: "..self.command,debugLevel)
	self.count = self.count or 0
	self.count = self.count + 1
	if self.count == self.retriesMax then
		scriptLog(", max retries "..tostring(self.retriesMax).." reached. Doing maxRetriesLua.  ",debugLevel)
		tempTimer(0,self.retriesMaxLua)
		return false
	elseif self.count > self.retriesMax then
		scriptLog(", max retries exceeded! Exiting.  ","error")
		return false
	end
	for i,v in pairs(self.triggers.success) do
		if self.triggers.success[i][2] then
			killTrigger(self.triggers.success[i][2])
		end
		self.triggers.success[i][2] = tempRegexTrigger(i,[[g.cwf:Complete(]]..self.command..[[),]].."'succeeded'"..[[,]]..debugLevel..[[);]]..self.triggers.success[i][1])
		scriptLog(", adding success trigger: "..i,debugLevel)
	end
	for i,v in pairs(self.triggers.fail) do
		if self.triggers.fail[i][2] then
			killTrigger(self.triggers.fail[i][2])
		end
		self.triggers.fail[i][2] = tempRegexTrigger(i,[[g.cwf:Complete(]]..self.command..[[),]].."'failed'"..[[,]]..debugLevel..[[);]]..self.triggers.fail[i][1])
		scriptLog(", adding fail trigger: "..i,debugLevel)
	end
	for i,v in pairs(self.timers) do
		killTimer(self.timers[i])
		self.timers[i] = nil
	end
	table.insert(self.timers,tempTimer(self.timeout,[[g.cwf:Complete(]]..self.command..[[),]].."'timed-out'"..[[,]]..debugLevel..[[);]]..self.timeoutLua))
end

--and when a trigger or timeout fires, it then calls g.cwf:Complete(command):

-- events: succeeded, failed, timed-out, aborted
function g.cwf:Complete(command,event,debugLevel)
	if not debugLevel then debugLevel = self.debugLevel end
	if not type(event) == "string" or not event then event = "aborted" end
	self = g.cwf.commandList[command] --overwrites "self" with command's current object
	scriptLog("\nCommand:"..self.command.." waterfall compelte:"..event,debugLevel)
	for i,v in pairs(self.timers) do
		killTimer(self.timers[i])
			scriptLog(", killed timeout of"..tostring(self.timout).." secs",debugLevel)
		self.timers[i] = nil
	end
	for i,v in pairs(self.triggers.success) do
		if self.triggers.success[i][2] then
			killTrigger(self.triggers.success[i][2])
			scriptLog(", killed success trigger:"..self.triggers.success[i],debugLevel)
			self.triggers.success[i][2] = nil
		end
	end
	for i,v in pairs(self.triggers.fail) do
		if self.triggers.fail[i][2] then
			killTrigger(self.triggers.fail[i][2])
			scriptLog(", killed fail trigger:"..self.triggers.fail[i],debugLevel)
			self.triggers.fail[i][2] = nil
		end
	end
	insertIntoHistoryTable(self.completeHistory,event)
	if event == "suceeded" then
		self.retries = 0
		self.failCount = 0
		self.timeoutCount = 0
	end
	if event == "failed" then
		if not self.failCount then self.failCount = 0 end
		self.failCount = self.failCount + 1
	end
	if event == "timed-out" then
		if not self.timeoutCount then self.timeoutCount = 0 end
		self.timeoutCount = self.timeoutCount + 1
	end
	if event == "aborted" then
		self.failCount = 0
		self.timeoutCount = 0
	end
	scriptLog(".  ",debugLevel)
end

Post Reply