This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Bab's profile picture

Problems with loadstring

Started by Bab, 22 January 2014 - 12:10 PM
Bab #1
Posted 22 January 2014 - 01:10 PM
I've spent the past couple of weeks making a turtle-driven, openPeripherals-reliant storage system.
The code works fine, but I'm running into some speed problems, namely, it's really slow after a lot of items are in the system.
Also, if the server would crash during an operation, the turtle would mess up the operation it was performing.
I would like to request some help from the pros for this.

My code:
http://pastebin.com/SyW4XLkJ

The main speed problems arise in the database functions after the turtle has been going for a while.
For example, having a lot of items slows down addItem, removeItem and readItemBack.
Retrieving a lot of items causes addEmpty and removeEmpty to become very slow.

Thanks in advance!
Edited on 06 February 2014 - 05:01 AM
Bomb Bloke #2
Posted 22 January 2014 - 07:06 PM
Regarding tables, it's possible to perform actions such as:

local myTable = {}
local myItem = "Cheese"  -- Why doesn't MineCraft have cheese yet?!

myTable[myItem] = {["id"] = id, ["chest"] = chest, ["storeSlot"] = storeSlot, ["maxStack"] = maxStack, etc}

local wantedItem = "Cheese"

print("Cheese is in chest "..myTable[wantedItem].chest)  -- Accessing example.

myTable["Cheese"] = nil  -- Remove the whole cheese table from myTable.

Lua converts your string to a hash, and uses that hash as a key to get directly to the value you want. Without the need to iterate when locating values in your tables, things tick along much faster (table.remove() for eg does a lot of looping, putting aside all the stuff you're doing manually). Granted, I cringe when I consider the difference hashmaps have on RAM usage, but a general rule of thumb is that processor time can often be traded for RAM, and vice versa - in this case the speed benefits are more than worth it.

You could furthermore use string.lower() to simplify user input, though that's more a matter of convenience then speed.

I've only skimmed your code, but there are other instances where you're going overboard with iteration. Let's take a look at eg editItemAmount():

function editItemAmount(id, chest, slot, amount)
  -- Open a file for read access.
  -- Iterate through the whole file, adding each line to a table.
  -- Close the file.
  -- Iterate through the table until a certain key is found, then change the value of that key.
  -- Open the same file for write access.
  -- Iterate through the whole table, writing each line back into the file.
  -- Close the file.
end

That's three loops right there. Why not one?

function editItemAmount(id, chest, slot, amount)
  -- Open a file for read access ("file1").
  -- Open a file with a different name for write access ("file2").
  -- Iterate through file1, writing each line directly into file2.
  --   * Check each value before writing, alter if the target key is found.
  -- Close both files.
  -- Delete file1.
  -- Rename file2 to file1.
end

Or better yet, why not just load the contents of all relevant files into RAM during your initialisation stage, and then never read from them again until the turtle reboots? I'm not even sure there's a pressing need to write changes to disk as often as they happen, but the constant read operations for data the turtle should already have certainly isn't doing you any favours.
Bab #3
Posted 23 January 2014 - 06:56 AM
First of all, thank you for responding.

Regarding tables, it's possible to perform actions such as:
local myTable = {} local myItem = "Cheese" -- Why doesn't MineCraft have cheese yet?! myTable[myItem] = {["id"] = id, ["chest"] = chest, ["storeSlot"] = storeSlot, ["maxStack"] = maxStack, etc} local wantedItem = "Cheese" print("Cheese is in chest "..myTable[wantedItem].chest) -- Accessing example. myTable["Cheese"] = nil -- Remove the whole cheese table from myTable.
Lua converts your string to a hash, and uses that hash as a key to get directly to the value you want. Without the need to iterate when locating values in your tables, things tick along much faster (table.remove() for eg does a lot of looping, putting aside all the stuff you're doing manually). Granted, I cringe when I consider the difference hashmaps have on RAM usage, but a general rule of thumb is that processor time can often be traded for RAM, and vice versa - in this case the speed benefits are more than worth it.

This would be a good idea, but multiple chest/slot combos need to be saved, because only ever having one stack of an item seems useless.

You could furthermore use string.lower() to simplify user input, though that's more a matter of convenience then speed.

Good point.

I've only skimmed your code, but there are other instances where you're going overboard with iteration. Let's take a look at eg editItemAmount():
function editItemAmount(id, chest, slot, amount) -- Open a file for read access. -- Iterate through the whole file, adding each line to a table. -- Close the file. -- Iterate through the table until a certain key is found, then change the value of that key. -- Open the same file for write access. -- Iterate through the whole table, writing each line back into the file. -- Close the file. end
That's three loops right there. Why not one?
function editItemAmount(id, chest, slot, amount) -- Open a file for read access ("file1"). -- Open a file with a different name for write access ("file2"). -- Iterate through file1, writing each line directly into file2. -- * Check each value before writing, alter if the target key is found. -- Close both files. -- Delete file1. -- Rename file2 to file1. end

This is brilliant, and this will be something I use a lot from now on. I never had the idea of using a second variable/table/file.

Or better yet, why not just load the contents of all relevant files into RAM during your initialisation stage, and then never read from them again until the turtle reboots? I'm not even sure there's a pressing need to write changes to disk as often as they happen, but the constant read operations for data the turtle should already have certainly isn't doing you any favours.

I'm doing this in an attempt to make the system more server-crash-proof.

As soon as I get the time, I'll edit the program to improve the amount of loops, but the other ideas I'll need some feedback on.
Edited on 23 January 2014 - 05:57 AM
Bomb Bloke #4
Posted 23 January 2014 - 09:19 AM
This would be a good idea, but multiple chest/slot combos need to be saved, because only ever having one stack of an item seems useless.
It's more the use of the technique to avoid iteration that I'm getting at. To expand on it, consider this block around 412 for eg:

Spoiler
                        for key,value in pairs(entries) do                                                                      --Insert the new stack into the list
                                if key > 2 then
                                        if tonumber(string.sub(value,1,string.find(value,",")-1)) == chest then
                                                if tonumber(string.sub(value,string.find(value,",")+1,string.find(value,":")-1)) == slot then
                                                        break
                                                elseif tonumber(string.sub(value,string.find(value,",")+1,string.find(value,":")-1)) < slot then
                                                        table.insert(entries, key, chest..","..slot..":"..amount)
                                                        break
                                                end
                                        elseif tonumber(string.sub(value,1,string.find(value,",")-1)) < chest then
                                                table.insert(entries, key, chest..","..slot..":"..amount)
                                                break
                                        end
                                        if key == table.getn(entries) then
                                                table.insert(entries, chest..","..slot..":"..amount)
                                        end
                                end
                                if key % 20 == 0 then
                                        sleep(0)
                                end
                        end

Now let's say that instead of having keys along the lines of 2,3,4,5,6,etc with strings attached to them as values, we used the strings as the keys (and assigned whatever value we like just for the sake of having them, eg "true"):

Spoiler
                        entries[chest..","..slot..":"..amount] = true

… and it's in the table. If for whatever reason you want to iterate through that table with a "for key,value in pairs(entries) do" loop, then you simply pay attention to "key" instead of "value" as you go. Wanna remove such entries? Just set 'em to nil.

You'll notice this loses your neat ordering, but I'm not sure why that's needed. Again, I'm skimming. In any case this skips all those slow table.insert()/remove()s that often work by, you guessed it, iterating (putting aside the loops you yourself are putting those calls in).

Another option: don't save strings in your "entries" table, save tables:

Spoiler
                        for key,value in pairs(entries) do                                                                      --Insert the new stack into the list
                                if key > 2 then
                                        if value.chest == chest then
                                                if value.slot == slot then
                                                        break
                                                elseif value.slot < slot then
                                                        table.insert(entries, key, {["chest"] = chest, ["slot"] = slot, ["amount"] = amount})
                                                        break
                                                end
                                        elseif value.slot < chest then
                                                table.insert(entries, key, {["chest"] = chest, ["slot"] = slot, ["amount"] = amount})
                                                break
                                        end
                                        if key == table.getn(entries) then
                                                table.insert(entries, {["chest"] = chest, ["slot"] = slot, ["amount"] = amount})
                                        end
                                end
                                if key % 20 == 0 then
                                        sleep(0)
                                end
                        end

Consider the difference in the amount of string searches and text conversions as your tables fill up with stuff. Heck, you could could combine both methods and use tables like these as your keys, if you were so inclined:

Spoiler
                        entries[{["chest"] = chest, ["slot"] = slot, ["amount"] = amount}] = true

I'm just nit-picking now, but "if key % 20 == 0 then" is also a bit slow (and only gets slower as "key" gets higher). Something like "if bit.band(key,31)==0 then" should be a fair bit faster - very loosely put, if "key" can be evenly divided by 32, it'll resolve as true, but no actual division is being processed.

I'm tempted to go on about ways to avoid table.insert()/table.remove() if you "must" stick with numeric indexing, but it's late and I'm already rambling.
Bab #5
Posted 23 January 2014 - 10:13 AM
This would be a good idea, but multiple chest/slot combos need to be saved, because only ever having one stack of an item seems useless.
It's more the use of the technique to avoid iteration that I'm getting at. To expand on it, consider this block around 412 for eg:

Spoiler
						for key,value in pairs(entries) do																	  --Insert the new stack into the list
								if key > 2 then
										if tonumber(string.sub(value,1,string.find(value,",")-1)) == chest then
												if tonumber(string.sub(value,string.find(value,",")+1,string.find(value,":")-1)) == slot then
														break
												elseif tonumber(string.sub(value,string.find(value,",")+1,string.find(value,":")-1)) < slot then
														table.insert(entries, key, chest..","..slot..":"..amount)
														break
												end
										elseif tonumber(string.sub(value,1,string.find(value,",")-1)) < chest then
												table.insert(entries, key, chest..","..slot..":"..amount)
												break
										end
										if key == table.getn(entries) then
												table.insert(entries, chest..","..slot..":"..amount)
										end
								end
								if key % 20 == 0 then
										sleep(0)
								end
						end

Now let's say that instead of having keys along the lines of 2,3,4,5,6,etc with strings attached to them as values, we used the strings as the keys (and assigned whatever value we like just for the sake of having them, eg "true"):

Spoiler
						entries[chest..","..slot..":"..amount] = true

… and it's in the table. If for whatever reason you want to iterate through that table with a "for key,value in pairs(entries) do" loop, then you simply pay attention to "key" instead of "value" as you go. Wanna remove such entries? Just set 'em to nil.

You'll notice this loses your neat ordering, but I'm not sure why that's needed. Again, I'm skimming. In any case this skips all those slow table.insert()/remove()s that often work by, you guessed it, iterating (putting aside the loops you yourself are putting those calls in).

Another option: don't save strings in your "entries" table, save tables:

Spoiler
						for key,value in pairs(entries) do																	  --Insert the new stack into the list
								if key > 2 then
										if value.chest == chest then
												if value.slot == slot then
														break
												elseif value.slot < slot then
														table.insert(entries, key, {["chest"] = chest, ["slot"] = slot, ["amount"] = amount})
														break
												end
										elseif value.slot < chest then
												table.insert(entries, key, {["chest"] = chest, ["slot"] = slot, ["amount"] = amount})
												break
										end
										if key == table.getn(entries) then
												table.insert(entries, {["chest"] = chest, ["slot"] = slot, ["amount"] = amount})
										end
								end
								if key % 20 == 0 then
										sleep(0)
								end
						end

Consider the difference in the amount of string searches and text conversions as your tables fill up with stuff. Heck, you could could combine both methods and use tables like these as your keys, if you were so inclined:

Spoiler
						entries[{["chest"] = chest, ["slot"] = slot, ["amount"] = amount}] = true

I'm just nit-picking now, but "if key % 20 == 0 then" is also a bit slow (and only gets slower as "key" gets higher). Something like "if bit.band(key,31)==0 then" should be a fair bit faster - very loosely put, if "key" can be evenly divided by 32, it'll resolve as true, but no actual division is being processed.

I'm tempted to go on about ways to avoid table.insert()/table.remove() if you "must" stick with numeric indexing, but it's late and I'm already rambling.

Thanks for replying again. The reason my lists are sorted is because the storage has no real upper capacity: you can just keep adding chests to the system. However, we don't want the system to waste chests by not using them, so everything has to be placed as close to the beginning as possible. The way I chose to implement it was by sorting the lists, but I don't know any other way to do this.
Bab #6
Posted 23 January 2014 - 02:21 PM
I've edited the code to incorporate your solution for editing a file wherever I thought it to be possible.
Edited on 23 January 2014 - 01:21 PM
Bab #7
Posted 23 January 2014 - 02:36 PM
I've only skimmed your code, but there are other instances where you're going overboard with iteration. Let's take a look at eg editItemAmount():

function editItemAmount(id, chest, slot, amount)
  -- Open a file for read access.
  -- Iterate through the whole file, adding each line to a table.
  -- Close the file.
  -- Iterate through the table until a certain key is found, then change the value of that key.
  -- Open the same file for write access.
  -- Iterate through the whole table, writing each line back into the file.
  -- Close the file.
end

That's three loops right there. Why not one?

function editItemAmount(id, chest, slot, amount)
  -- Open a file for read access ("file1").
  -- Open a file with a different name for write access ("file2").
  -- Iterate through file1, writing each line directly into file2.
  --   * Check each value before writing, alter if the target key is found.
  -- Close both files.
  -- Delete file1.
  -- Rename file2 to file1.
end

Err, I changed the code for this like I said, but I didn't realise an error: The first two lines of the code contain the total amount of the item in the system and the amount of slots the item occupies. I can't possibly change those amounts without reading the file first, so I can't write until after I read in editItemAmount specifically. For the others, this is fine.
Edited on 23 January 2014 - 01:57 PM
Bab #8
Posted 02 February 2014 - 05:37 PM
After a lot of fiddling and thinking on and off, I finally figured out the way of thinking you meant.
Now I have a new question: I am currently using the storage system on my own server. I have amassed around 320 different kinds of items, and of some items I have more than a hundred stacks.
I'm thinking of storing it all in one giant table: items.<item id>.<chest>.<slot> = <amount>
This would cause items to have hundreds of subtables, which (for the big numbers) have about 5 subtables, which have about a hundred subtables.
How bad would this be? How much ram would this even take?
Then I would need to save it using textutils.serialize every time I add an item.
Would this blow up my computer every time I add a new item? Would it take hours?

All in all: is that feasible to use or should I use something different?

PS.
If anyone knows a way to have the database secure when the server crashes that doesn't involve saving every time the database is edited, I would love to hear it.
Edited on 02 February 2014 - 04:41 PM
surferpup #9
Posted 02 February 2014 - 05:43 PM
You could have a table of items each which reference an individual itemID table. Then when you make changes, you only need to serialize and store the changed itemID table.
Bab #10
Posted 02 February 2014 - 05:52 PM
How would I handle the referencing?
surferpup #11
Posted 02 February 2014 - 06:01 PM
Here is the pseudo-code


items = {}

-- filename is itemID

for each filename in item file directory
	insert into items table: filename as key, unserialized filename as table value
end

Then you have key,value pairs for each itemID. You know the file name to serialize back to, and you minimize the amount that you are storing back to disk.
Edited on 02 February 2014 - 05:02 PM
Bab #12
Posted 02 February 2014 - 06:03 PM
Thanks for the response. I will try to make something out of it tomorrow after I do some more research on how to handle table values etc. as I was not aware you could use them like pointers.
surferpup #13
Posted 02 February 2014 - 06:07 PM
They aren't really pointers, they are actually assignments at a point in time. You would still make changes to items.itemID.chest.slot = 26 or whatever. You only store back the items.itemID table. I hope that makes sense.
surferpup #14
Posted 02 February 2014 - 06:22 PM
You also asked how much memory this would take. If it takes too much, just load/unserialize each item is as the active item when you need to mess with it and serialize it back for changes. To keep track of which items you already have stored, just keep a table of the itemIDs – just the ids, not info).
Edited on 02 February 2014 - 05:26 PM
Bomb Bloke #15
Posted 02 February 2014 - 06:58 PM
If anyone knows a way to have the database secure when the server crashes that doesn't involve saving every time the database is edited, I would love to hear it.
This may not be feasible in your setup and would likely involve a fair bit of re-writing even if it is, but…

… I'm running a similar setup, where I've got a large number of containers (barrels, actually) and a turtle which manages them. Thus far there's 128 barrels in the system, thus it's able to store that many different types of items, in quantities of up to 64 stacks each (anything more then that either goes into an enderchest leading to another player's AE system, or if I don't think he'd find it useful, into a void pipe - my view is that you can have too many items).

There are two "server" computers, each with a wireless modem, and connected to 64 barrels via wired modems. The turtle talks to these whenever it wants to know how many items are in a given barrel. It could go check itself, but that'd be slow.

Currently all the barrels are in use so I'm thinking of adding more - the only real limit is my patience for placing barrels and pipes. Though I suppose I could get a turtle to do that. Heh.

While I doubt you'd want to use this exact system, you can probably see how to adapt something similar to eliminate storage-related file system usage from your script. Even if your "servers" were only there to build fresh, up-to-date tables and transmit them to the turtle once for every MineCraft server restart, think of all the reads/writes you wouldn't be wasting time with…
surferpup #16
Posted 02 February 2014 - 07:20 PM
That's a cool idea, Bomb Bloke. Essentially you are using the barrels as the database, and allowing the barrels to handle the read/writes to disk. Nice.
Bomb Bloke #17
Posted 02 February 2014 - 07:42 PM
Yep, though my point is that the same system would work just as well with chests. It's just that with barrels, it becomes really easy for me to just waltz down the middle of my storage center and grab what I want, and they look much more interesting to boot.
Bab #18
Posted 03 February 2014 - 12:10 AM
Actually Bomb Bloke, I think I'm gonna go with that. I don't mind the rewrite, I gotta have something to do.
However, I think I'm gonna have the turtle build the table itself by just going through all chests. It's easier to set up that way.
Thanks for the help.
mibac138 #19
Posted 03 February 2014 - 10:50 AM
Crash proof:

--Main Loop--
	while true do
		ok, err = pcall(parallel.waitForAll(
			function()
				while true do
					loot()
					sleep(0)
				end
			end,
			function()
				while true do
					ping()
					sleep(0)
				end
			end
		))
				if not ok then print(err) end
		sleep(0)
	end
Edited on 03 February 2014 - 09:51 AM
Bab #20
Posted 03 February 2014 - 04:00 PM
Thanks for your reply, but I meant server crash proof, as in, the database must be correct at all times, and that issue will be solved by Bomb Bloke's approach.
Bab #21
Posted 04 February 2014 - 01:10 PM
I have a small and quick question: Can you peripheral.wrap the block to the right of a wireless turtle somehow? (as in, don't wrap the modem)
Lyqyd #22
Posted 04 February 2014 - 02:49 PM
Sure.


turtle.turnRight()

Otherwise, no. You can't refer to two different peripherals with the same side name ("right").
Bab #23
Posted 04 February 2014 - 03:52 PM
Figures. No biggie, just makes the system a tad slower. Thanks for the response.
Bab #24
Posted 06 February 2014 - 06:07 AM
I'm currently making the remote control part of the program.
I've got this running in the server: http://pastebin.com/ZQXP2mxX
and this on the turtle itself: http://pastebin.com/SyW4XLkJ

Now, whenever I try to send a command to the turtle, it crashes at around line 821, with the error attempt to call nil.
The command does get sent correctly, as displayed by the print, e.g. if I do ||flush the command return flushChest(1) gets sent, which should be executed on the turtle.

What am I doing wrong?
Bomb Bloke #25
Posted 06 February 2014 - 06:45 AM
Messing around in the Lua prompt suggests to me that the function loadstring() returns doesn't have access to your flushChest() function. It would be able to find _G.flushChest(), though.

Edit:

That said, I'm not sure redefining your functions that way would be the "best" solution. The way I would go would be to put all the functions you want to call "remotely" in a table, eg:

local myFunctions = {

["flushChest"] = function(enderChest)
	--Empty the entire Ender Chest.
	etc
end,

.
.
.

["playerFlush"] = function(player, glasses)
	sendCommand("flushChest(1)")
	etc
end}

Now say you send a rednet message with a table with two indexes; a function name, and another table containing your argument(s) for that function:

rednet.send(turtleID,{"flushChest",{1}})

On the receiving end the function can now be called without loadstring:

id, cmd = rednet.receive()
myFunctions[ cmd[1] ]( unpack(cmd[2]) )

I can't say whether this is the best solution either, but it'd something to consider.
Edited on 06 February 2014 - 06:02 AM
CometWolf #26
Posted 06 February 2014 - 10:06 AM
Seeing as his functions are not locally defined, your first suggesiton wouldn't make much of a difference. The way to deal with it if they were however, would be to use setfenv to change the environment to one we create, so we know the name

-- this goes at the start of the program, so that we know the variable name of the environment in use.
_G.programEnv = {} --create new environment table
setmetatable(_G.programEnv,{__index = _G}) --inherit global
setfenv(1,_G.programEnv) --change current environment to the new one
-- the rest goes wherever.
local function herpDerp()
  print"herpDerp"
end
local func = loadstring("_G.programEnv.herpDerp()")
func()
Edited on 06 February 2014 - 09:10 AM
Bomb Bloke #27
Posted 06 February 2014 - 06:53 PM
Erm, I may have mis-phrased that. I meant that it would've been able to find _G.flushChest() if he specifically defined it under that name (he'd still be using the same loadstring function call, which looks for just "flushChest()").

Dumping functions directly into G is messy, of course. Your way seems to allow for an easy cleanup by simply wiping your programEnv table before it exits.
Bab #28
Posted 14 February 2014 - 01:53 PM
Hello again. Sorry for taking so very very long at getting back to you, but my laptop died and I had to get it repaired.
Now that I've returned, I've tried to implement CometWolf's suggestion, which resulted in this:
lootTurtle: http://pastebin.com/SyW4XLkJ
itemManager: http://pastebin.com/ZQXP2mxX

It now errors with this: nil: lootTurtle:828: attempt to call nil.

What did I overlook?
OReezy #29
Posted 14 February 2014 - 02:39 PM
local response = f()
I don't see where you've defined f() anywhere so its probably nil.
Edited on 14 February 2014 - 01:40 PM
Bab #30
Posted 14 February 2014 - 02:42 PM
It should be defined at line 824: loadstring returns a function as the first value, which I have called f.
Lyqyd #31
Posted 14 February 2014 - 02:49 PM
What was the message it received?
Bab #32
Posted 14 February 2014 - 02:59 PM
The message passed into loadstring would have been
return _G.programEnv.getIDs("Cobblestone")
Edit: I double checked to make sure and this is exactly what the string message that's passed into loadstring contains.
Edited on 14 February 2014 - 02:03 PM
CometWolf #33
Posted 14 February 2014 - 04:08 PM
It's because the function getIDs is defined locally.
Bab #34
Posted 14 February 2014 - 04:10 PM
Didn't you do the exact same thing here?

Seeing as his functions are not locally defined, your first suggesiton wouldn't make much of a difference. The way to deal with it if they were however, would be to use setfenv to change the environment to one we create, so we know the name

-- this goes at the start of the program, so that we know the variable name of the environment in use.
_G.programEnv = {} --create new environment table
setmetatable(_G.programEnv,{__index = _G}) --inherit global
setfenv(1,_G.programEnv) --change current environment to the new one
-- the rest goes wherever.
local function herpDerp()
  print"herpDerp"
end
local func = loadstring("_G.programEnv.herpDerp()")
func()
CometWolf #35
Posted 14 February 2014 - 04:13 PM
Indeed i did, my bad. I didn't test that code.
Bab #36
Posted 14 February 2014 - 04:21 PM
So, what should I do then? The same without making the functions local?
CometWolf #37
Posted 14 February 2014 - 04:30 PM
Yes.
Bab #38
Posted 14 February 2014 - 04:38 PM
Same error, but now it's located at nil: nil
So the full error message is now
nil: nil: attempt to call nil
which is very helpful :rolleyes:/>
CometWolf #39
Posted 14 February 2014 - 04:58 PM

				local f, errorMessage = loadstring(message)
				if errorMessage ~= nil then
						error(errorMessage)
				end
this will only catch compile errors, not runtime errors.


				local f, errorMessage = loadstring(message)
				if errorMessage ~= nil then
						error(errorMessage)
				else
						err, res = pcall(f)
						if not err then
						  error(res)
						end
						return res
				end
Edited on 14 February 2014 - 03:59 PM
Bab #40
Posted 14 February 2014 - 05:03 PM

				local f, errorMessage = loadstring(message)
				if errorMessage ~= nil then
						error(errorMessage)
				end
this will only catch compile errors, not runtime errors.


				local f, errorMessage = loadstring(message)
				if errorMessage ~= nil then
						error(errorMessage)
				else
						err, res = pcall(f)
						if not err then
						  error(res)
						end
						return res
				end

Wouldn't err and res be the other way around?
Assuming they should: That's useless, as I would still have to execute the nil function.
A quick test gave me the same result as before.
Edit: I found the source of this last bug. A simple wrong call.
Now that this part is working, I'll be busily messing around with the rest of the code. I'll post again if I need help with anything else.
Huge thanks to everyone who answered!
Edited on 14 February 2014 - 04:16 PM
CometWolf #41
Posted 14 February 2014 - 05:18 PM
pcall returns a false and an error message if the function it calls errors, otherwise it returns true and whatever the function returns. So no, they are correct. Please don't try to correct me based on assumptions.
Bab #42
Posted 14 February 2014 - 05:23 PM
Sorry!
I didn't mean to offend you in any way, I was just saying that it didn't work for me. Should have phrased it a little differently.
Edit:
Oh dear, I missed the pcall completely. That's why that didn't work then.
Edited on 15 February 2014 - 10:16 AM