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

Keep Computers loaded

Started by Noblauch, 13 November 2012 - 02:05 AM
Noblauch #1
Posted 13 November 2012 - 03:05 AM
Hey you guuuuys! :o/>/>

I just signed up, and have a question :P/>/> I'm running a Tekkit server and, just built a Nuclear Reactor o.o using: ComputerCraft
It works fine, and I'm very happy with the control possibilities :)/>/> But there is a HUGE problem, wherefore the whole thing is almost useless..
If I step out of the chunks for a given time or go offline, the computer shuts down or stops the program.. No Chunkloaders work for keeping the PC operating..

Is there any possibility to get the computer working without have to stand next to it all the time? Any configs or so?
Or maybe with with redstone?

It would be great, cause this fact makes the Reactor almost useless :D/>/> :(/>/>

Thank you in advance!
Noblauch
KaoS #2
Posted 13 November 2012 - 03:46 AM
just keep it in the startup file on the computer and make it save info to a file, that way when it is reloaded it will resume where it left off
BigSHinyToys #3
Posted 13 November 2012 - 03:57 AM
To explain in more detail the program names "startup" will be run every time the computer reboots so every time the computer is reloaded into existence chunks.
Noblauch #4
Posted 13 November 2012 - 04:09 AM
And what if the computer shuts down? When I use the startup I have to reactivate the computer every single time, and even if the computer starts over again it will only work when I'm standing near to it, not when I'm off/ away :S
So I guess there is no possibility to make it work on its own?
But thanks for the response so far :P/>/>
BigSHinyToys #5
Posted 13 November 2012 - 04:24 AM
And what if the computer shuts down? When I use the startup I have to reactivate the computer every single time, and even if the computer starts over again it will only work when I'm standing near to it, not when I'm off/ away :S
So I guess there is no possibility to make it work on its own?
But thanks for the response so far :P/>/>
you should give it a try before saying it wont work
Cranium #6
Posted 13 November 2012 - 05:39 AM
Railcraft chunkloaders should work to keep the computers running unless they are disabled on the server.
Tclord #7
Posted 14 November 2012 - 07:44 AM
You mention saving and reloading the state on reboot. Can you detect when the chunk is about to unload somehow so you can save the state just before it shuts down?
KaoS #8
Posted 14 November 2012 - 07:49 AM
just keep saving it whenever the state changes. it hardly uses any resources. I'll post a script for it in a minute

EDIT: here we go. run this program and it will save the table tRecord to the filename specified at the top whenever you add a value to it,if you modify a value just call the table like so: tRecord() and it will save and you can also call it like this: tRecord(index,val) and it will essentially say tRecord[index]=val for you and save it too


local filename='record'
local function save(t,index,val)
  if val then
   rawset(t,index,val)
  end
  local oFile=io.open(filename,'w')
  oFile:write(textutils.serialize(t))
  oFile:close()
end
tRecord=setmetatable({},{__index=function(tMe,index)
  if fs.exists(filename) then
   local oFile=io.open(filename,'r')
   local temp=textutils.unserialize(oFile:read())
   oFile:close()
   tMe[index]=temp[index]
   return temp[index]
  end
  return nil
end;
__newindex=save;
__call=save})
Edited on 14 November 2012 - 07:14 AM
Tclord #9
Posted 14 November 2012 - 05:56 PM
Hmm, interesting.. But complex looking too. Can you break down what's going on there so I can understand it? I've never heard of calling a table like a function before but that sounds like an awesome thing to understand how to do.
KaoS #10
Posted 14 November 2012 - 11:21 PM
I am using metatables. you can use metatables to specify how a table should behave in certain situations. here is a great tutorial on metatables.

I will now make my usual feeble attempt at actually explaining my code (explanations really aren't my strong suit as I end up thinking faster than I type/talk and confusing people…. sorry in advance :P/>/>)

once you have read the tutorial above you will understand that the second table in the setmetatable command's parameters contains information about how the tRecords table should behave. the 3 situations I am preparing it for are: if you try to index (v: access) a part of the table that has no value (__index), if you try to make a new index (n: value/entry) in the table that was previously nil (__newindex) and if you call the table as a function (__call)

both the newindex and call metatables just call the save function which is pretty easy to understand, it checks if you specified a value to set, if you did then it sets it (there will always be a value in the __newindex case of course) and once that is done it then saves the table to a file (using the filename at the top of the script)

the index metatable is the important part as this opens the specified file and retrieves the saved info there so you can use it in your code

just so you also know the rawset and rawget commands can be used to add/retrieve info from a table and ignore the existing metatable when you do. this is VERY important. here is an example where it is needed. you have a metatable that changes what happens when you add a value to a table, this metatable calls a function that saves that index on a DIFFERENT table as a backup or copy/whatever and then also saves it to the normal table. if you do not use rawset to save the value to the normal table then you are defining a new index right there and that will call the metatable all over again and continue doing so in a loop that will freeze and eventually crash your program….

———————————————————–———————————————————————————————

That being said there are 3 major problems with this code. firstly I do not know of any metatable that allows you to change what happens when you change an EXISTING value so if you make a new index

tRecord[1]='test01'
it will save that to the file, then when you change that existing value

tRecord[1]='test02'
it will change the value in the table but it will not save that value to file, this is why I allowed you to call the table as a function in stead… there is a way that I could make it work but it would make the code a little messy. I will post it with this method at the bottom of this post

second problem: the metatable only looks in the file if there isn't an existing value in tRecord then it will not look in the file as it only looks when you try to index a nil value (once again I do not know of a metatable for any indexing). this can actually be incredibly useful as you can use rawset to add a value to the table that will then ignore what is in the file FOR THAT SESSION ONLY

third problem: you must recall something from the table before adding a value or the file will be overwritten with the empty table. this is my fault and is a stupid mistake, it will be corrected in the code to follow

———————————————————–———————————————————————————————


function addRecord(filename)
  local function recall(t,index)
   if fs.exists(filename) then
	local oFile=io.open(filename,'r')
	local temp=textutils.unserialize(oFile:read())
	oFile:close()
	return index and temp[index] or temp
   end
   return not index and {} or nil
  end

  local function save(t,index,val)
   local temp=recall()
   if val then
	temp[index]=val
   end
   local oFile=io.open(filename,'w')
   oFile:write(textutils.serialize(temp))
   oFile:close()
  end
  return setmetatable({},{__index=recall;__newindex=save;__call=save})
end

I think this code is as good as it will get…. in this case we now have a function (addRecord) and you use it like so


myTable=addRecord('stored')

and myTable becomes a table that will store and recall information from the file 'stored'. it will no longer get confused when modifying an existing value, there is no problem with loading it before changing a value and this way you can use tables of a name you choose… enjoy

EDIT: forgot to mention… fully tested and functional. you can even have multiple tables pulling from the same file
Edited on 14 November 2012 - 10:22 PM
Tclord #11
Posted 16 November 2012 - 07:20 AM
I think I get the idea. I am a little confused about a few points due to my knowledge of programming in other languages, though. Your addRecord() function uses the parameter filename. My instincts tell me that this variable would exist (be in scope) only until we hit the return statement and then would no longer exist. However you have subfunctions using this variable that would be called after the variable would no longer exist. You said you've tested this code so I have to assume that the variable filename says in existance. I don't understand why or what the rules are regarding the lifetime of variables and parameters.

Secondly, from what I can see, it would appear that getting a table value will load the old table from a file but only keeps one one specific key, value pair. Setting a new value in the table saves the current table. At no time is the old table fully loaded from the file. So it seems like only the keys you access prior to your first key set will be salvaged from the file. Shouldn't you do something like this in addRecord() instead?

return setmetatable(recall(), { __index=recall; __newindex=save; __call=save })

This way it would restore the original table from file when first creating the table which would ensure none of the key/value pairs are lost.

Also, since the metatable is a table as well, couldn't you just store all the actual data in the metatable instead of the table and just leave the table empty all the time? That way you can ensure that your functions are always called, and you can still retrieve the saves values from the metatable since that is where you would store the data. Did that make sense? Here's some code I came up with that illustrates what I mean:


function savedTable(filename)
  local function save()
    local oFile = io.open(filename, 'w')
    oFile:write(textutils.serialize(temp))
    oFile:close()
  end
  local function load()
    local iFile = io.open(filename 'r')
    local data = textutils.unserialize(iFile:read())
    iFile:close()
    return data
  end
  local function get(t, index)
    return getmetatable(t).index
  end
  local function set(t, index, value)
    getmetatable(t).index = value
    save()
  end
  local t = load()
  t.__index = get
  t.__newindex = set
  t.__call = save
  return setmetatable({}, t)
end

I haven't tested this yet so not sure it even works but I think it is correct.
KaoS #12
Posted 16 November 2012 - 07:37 AM
I think you missed an important part of my code. when you add an index it recalls the table from the file, adds the index and then saves it to the file again so you do not lose any information there


Also, since the metatable is a table as well, couldn't you just store all the actual data in the metatable instead of the table and just leave the table empty all the time?

That is what I am doing, no values are ever written to the actual table. whenever you try to get a value it recalls it from the file and returns it without actually writing it to the table so next time it still reads the file

EDIT: oh and the variable 'filename' will remain loaded as long as something is referencing it.
so if it is in the metatable then it will remain loaded
Edited on 16 November 2012 - 06:40 AM
Espen #13
Posted 16 November 2012 - 03:20 PM
[…] firstly I do not know of any metatable that allows you to change what happens when you change an EXISTING value […]
Hey KaoS, just wanted to add that there is a way to keep track of all table changes, regardless if it's a new index or not.
It works by way of a proxy table that stays always empty, so that all accesses can be recognized.
It then redirects the access to the original table.
Lua PIL - 13.4.4 – Tracking Table Accesses

But there's a drawback nonetheless, i.e. you can't iterate the table via pairs(), because you'll only iterate through the proxy table (which is always empty) and not the original table.
I bet one could hack together a workaround though, so in the end I guess it matters what's more important to a specific project or what's used in greater numbers
That is: Easy tracking or easy iterating. :P/>/>
Edited on 16 November 2012 - 02:22 PM
KaoS #14
Posted 16 November 2012 - 03:35 PM
Thanks man. you know this would have been perfect if you had mentioned it 2 days ago :P/>/>

would've saved me a while of frustrated fiddling 'till I figured that out. that's how it works now. did you know that the ipairs function should still work on the proxy table? and I recently learned how to use a for in loop however I want in addition to the existence of the __mode metatable which I have yet to figure out but I think it will be able to sort that out too…. good article though, thanks again
Tclord #15
Posted 16 November 2012 - 06:35 PM
Ok, I think I finally understand your code. I'm used to C++ so it's a little tricky to get my head around some of this stuff. Thanks for all the explainations.
Espen #16
Posted 17 November 2012 - 12:00 AM
@KaoS:
Unfortunately it isn't as easy as using ipairs instead of pairs.
It does basically the same as pairs, with the difference that it iterates through numbered keys only while ignoring any other keys and it stops at the first nil value.
Apart from that it doesn't do anything special to access tables, so even if you use ipairs on a proxy table, it will still try to iterate through that one directly instead of the real table behind it.
Therefore the behaviour that is to be expected when trying to use ipairs on a proxy table is that it would try to access index 1, find it empty and thus return nothing.

Edit: ipairs does work after all, thanks KaoS for correcting me.

@Tclord:
Hehe, only in the beginning. Once you begin to see the table-ception that is Lua (everything is basically tables, tables-in-tables, tables about tables, etc.), it won't be that tricky longer. That is, compehension of Lua principles, not necessarily being able to write good code with it, as that is never-ending practice. :)/>/>
Edited on 17 November 2012 - 01:36 AM
Kingdaro #17
Posted 17 November 2012 - 12:14 AM
@Tclord:
Hehe, only in the beginning. Once you begin to see the table-ception that is Lua (everything is basically tables, tables-in-tables, tables about tables, etc.), it won't be that tricky longer. That is, compehension of Lua principles, not necessarily being able to write good code with it, as that is never-ending practice. :)/>/>
You inspired me to make this:
Espen #18
Posted 17 November 2012 - 12:30 AM
You inspired me to make this:
- snip -
What have I done! :D/>/>

Spoilerjk, jk :)/>/>
KaoS #19
Posted 17 November 2012 - 01:50 AM
ah but you see Espen it would try to access the index 1 and it would get a value back because the __index metatable would look up the correct value and return it so it should work fine
Espen #20
Posted 17 November 2012 - 02:32 AM
ah but you see Espen it would try to access the index 1 and it would get a value back because the __index metatable would look up the correct value and return it so it should work fine
You're absolutely right, that was a derp-up of my understanding then. My apologies.
Thanks for clearing that up. :D/>/>

Edit #1: While looking up the func-desc for pairs and ipairs again, I noticed that apparently If the table passed to pairs has a metamethod __pairs, pairs calls that function with the passed table as argument.
So in principle, it should be relatively easy to just implement one's own iterating function within __pairs and thus make pairs work for proxy tables too, no?
Hmm, this got me really curious, gonna have to play around with metatables.^^

Edit #2: Unfortunately I just noticed in bios.lua that the pairs of CC does not look for the __pairs metamethod at all, but simply returns the default values (next, table and nil). :)/>/>
So one would first have to overwrite the CC pairs function first, either via _G.pairs or by harcoding the changes into bios.lua
Ah well, that's what everyone's personal Utilities-API is for, right?^^

Edit #3: Ok, just fiddled around and it seems to work.
The original pairs method in the bios.lua, which doesn't yet implement the __pairs metamethod looks like this:
Spoiler
function pairs( _t )
  local typeT = type( _t )
  if typeT ~= "table" then
    error( "bad argument #1 to pairs (table expected, got "..typeT..")", 2 )
  end
  return next, _t, nil
end

But after a little condition-check one can implement one's own __pairs metamethod for proxy-tables in order to iterate through them with pairs():
Spoiler
-- Full-featured pairs()
function pairs( _t )
  local typeT = type( _t )
  if typeT ~= "table" then
    error( "bad argument #1 to pairs (table expected, got "..typeT..")", 2 )
  end
  local mt = getmetatable( _t )
  if mt and mt.__pairs then
    return mt.__pairs( _t )
  else
    return next, _t, nil
  end
end

-- create private index
local index = {}

-- create metatable
local mt = {
  __index = function (t,k)
    print("*access to element " .. tostring(k))
    return t[index][k]   -- access the original table
  end,

  __newindex = function (t,k,v)
    print("*update of element " .. tostring(k) .. " to " .. tostring(v))
    t[index][k] = v   -- update original table
  end,

  __pairs = function (t)
    print("*pairs-access of " .. tostring(t[index]))
    return next, t[index], nil
  end
}

-- creates a proxy table for the passed table t
function track (t)
  local proxy = {}
  proxy[index] = t
  setmetatable(proxy, mt)
  return proxy
end

--[[ EXAMPLE ]]
local tbl = {}
tbl = track(tbl)

tbl[1] = "Works"
tbl.a = "Hello"
tbl.a = "World"  -- overwriting a for good measure
tbl.z = "Yes!"
tbl[4] = "Diamonds"
tbl.ggg = 23

for k, v in ipairs(tbl) do print(tostring(k).." -> "..v) end
-- Output: '1 -> Works' (but not '4 -> Diamonds', since [2] is already nil)

for k, v in pairs(tbl) do print(tostring(k).." -> "..v) end
-- Output: All values.
Edited on 17 November 2012 - 02:21 AM
KaoS #21
Posted 17 November 2012 - 10:10 AM
good code there but why modify the BIOS version? I would just make my own separate copy that I use instead. and just copying the function would probably be a better idea, knowing CC devs 'next' would be a local function…. I would say


local oldp=pairs
function pairs(t)
  if type(t)=='table' then
	return (getmetatable(t).__pairs and getmetatable(t).__pairs(t) or oldp(t))
  end
end

PS: I'm half asleep at the moment. sorry if I derped

EDIT: yep… derped and corrected thanks to Tclord
Edited on 18 November 2012 - 08:54 AM
Espen #22
Posted 17 November 2012 - 04:37 PM
good code there but why modify the BIOS version? […]
PS: I'm half asleep at the moment. sorry if I derped
No reason really, just wanted to point out the change of the code itself without suggesting any particular way of how to implement it.

And hey no biggie, I know pretty well how derpy one can become when tired. So no biggie, we're all just humans and improvement takes derpies.
In fact, it's 4:30 in the morning where I am… still. So I'm dangerously close to derp-mode and have to restrain myself from post-haste, if you know what I mean. :)/>/>
Sammich Lord #23
Posted 17 November 2012 - 10:00 PM
good code there but why modify the BIOS version? […]
PS: I'm half asleep at the moment. sorry if I derped
No reason really, just wanted to point out the change of the code itself without suggesting any particular way of how to implement it.

And hey no biggie, I know pretty well how derpy one can become when tired. So no biggie, we're all just humans and improvement takes derpies.
In fact, it's 4:30 in the morning where I am… still. So I'm dangerously close to derp-mode and have to restrain myself from post-haste, if you know what I mean. :)/>/>
Nobody is a human beside meh. Stop calling yourselves humans.
KaoS #24
Posted 17 November 2012 - 10:03 PM
–snip–
Nobody is a human beside meh. Stop calling yourselves humans.

I would never claim to be human I assure you :D/>/> I have learned to ignore accusations of being one too :)/>/>
Tclord #25
Posted 18 November 2012 - 06:26 AM
good code there but why modify the BIOS version? I would just make my own separate copy that I use instead. and just copying the function would probably be a better idea, knowing CC devs 'next' would be a local function…. I would say


local oldp=pairs
function pairs(t)
  if type(t)=='table' then
	return (getmetatable(t).__pairs and getmetatable(t).__pairs(t) or pairs(t))
  end
end

PS: I'm half asleep at the moment. sorry if I derped

Ya, I think you need to use oldp(t) after the or, yes? Otherwise this would be infinate recursion? If not, I'd love to know what I am missing here. Thanks.
KaoS #26
Posted 18 November 2012 - 09:53 AM
ahah. knew I derped :)/>/> thanks for pointing out where. post edited
Lewis.holcombe #27
Posted 25 February 2013 - 01:30 PM
This might be wrong but can't you put an anchor down then run a redstone signal to it attach it to a timer set to like a few mins then program the computer to execute a command (eg startup) every time it receives a signal?
I would so the program for you but it's 12:34 and I'm on my iPad :)/>
Look into it :)/> hope I helped!
Tiin57 #28
Posted 26 February 2013 - 12:38 AM
This might be wrong but can't you put an anchor down then run a redstone signal to it attach it to a timer set to like a few mins then program the computer to execute a command (eg startup) every time it receives a signal?
I would so the program for you but it's 12:34 and I'm on my iPad :)/>/>
Look into it :)/>/> hope I helped!
Bad necro.
Cranium #29
Posted 26 February 2013 - 01:59 AM
Bad necro.
Then don't reply to it, just report it. geez….
And no, chunkloaders can't turn on a computer.