Posted 03 October 2015 - 12:01 AM
XT API Version 2.0
This is an API to aid in persistence and turtle position tracking (mostly, there are other misc features).
A few ages ago, I posted an API similar to this, also called XT, but mainly based on wireless repeating and turtle position tracking.
Then, rednet included repeating, and I noticed my position tracking was failing sometimes.
Yet, I got so used to calling xt.fw instead of turtle.forward, that I kept using it for everything, until the old code got so rusty I decided to scrap it and start again.
So here it is.
Main Features
-VERY robust serialization-Automatic value saving
-Prioritized stackgroup system
-Automatic startup setting and old startup handling
-Accurate turtle position saving (thanks lama!)
-Position and facing management (goto()'s and face())
-All of turtle's functions, some enhanced and shortened
-Navigation (works best on overworld-like terrain)
-Stackgroup-based movement listeners
-Easily include in programs, using inline downloader or extractor
This is a big API, so it's separated into 5 parts, each with its own documentation.
The full documentation is a bit large, so I suggest reading the shortened version:
Short Documentation
Before anything, you should call xt.setTurtleUser(<unique string>)The string must be unique to your program, like "jake's_quarry_script"
After setting your turtle user, you can do the most 2 important things.
Turtle Moving
Now that the turtle user is set, you can call any movement function, like:
xt.fw, xt.bk, xt.up, xt.dn: All functions to move forward, back, up or down
xt.right, xt.left: Functions to turn right or left
And 4 other special functions: xt.ffw, xt.fbk, xt.fup, xt.fdn
These will not fail, and will insist until the move gets done.
To tell it what to do when it cant move, you call:
xt.fpushForcemove("main",<xt.forceDig or xt.forceAttack>)
Whatever you choose, you can only choose one. If you wish to do both:
xt.fpushForcemove("dig",xt.forceDig)
xt.fpushForcemove("atk",xt.forceAttack)
Your current position is at xt.x, xt.y and xt.zY is the height, and these are saved across reboots, and updated at every turn.
xt.f is your facing. This is saved across reboots and updated at every move.
If you wish to update your facing, you can do xt.orientDigging()
This will dig blocks, and uses a torch or ladder to find the current orientation.
Value Saving
Now this might be a little bit more useless, but it is very useful for persistence.
Once you have set your user, you can add values.
A value is basically a variable saved across reboots.
To add one, do xt.addValue(<valueName>,<defaultValue>)
The valueName is what you want to call it. defaultValue is the value you want to assign if it didn't exist before.
For example:
xt.addValue("myVal",1)
print("myVal is "..xt.myVal)
xt.myVal=xt.myVal+1
Every time you run this program, the value will increase, even across reboots, crashes, chunk unloads or singleplayer logout.The values added are accessible on xt.<valueName> and every time they are assigned something, they are saved to disk.
But if you want to waste your time, or learn everything, here it is. [WARNING: I'm not very good explaining]
Full Documentation
I suggest you read only what you want to know.IMPORTANT: Before moving/turning you MUST call xt.reloadStacks()!
Turtle
XT includes an exact copy of every value contained in the turtle API, but the functions might be different, and some extra functions are contained.Most important functions:
xt.fw = xt.forward = turtle.forward
xt.bk = xt.back = turtle.back
xt.dn = xt.back = turtle.down
xt.up = xt.back = turtle.up
xt.left = xt.turnLeft = turtle.turnLeft
xt.right = xt.turnRight = turtle.turnRight
Calling any of these functions will call a set of listeners, before and after doing the move/turn.
You can add listeners using the stackgroup system.
It's not necessary to read this, only if you want to use stackgroups.
Explanation of stackgroups from turtle's point of view
There are multiple stackgroups, the premove, postmove, preturn and postturn stackgroups.Each of them are for storing listeners for different situations. As you might infer, premove listeners are called before moving, postmove listeners after, and the same with turns.
Basically, a stackgroup is a group of named stacks. It can be better explained in this image:
Here, there are 3 stacks, and each one has a "top" element, the green element.
If this stackgroup were called, each green function would be called.
Imagine this stackgroup was the premove stackgroup. To make it print "worse" instead of "bad", you would do:
xt.pushPremove("hello",function()print("worse")end)
And, to make it print "bad" again, you would do:xt.popPremove("hello")
If we would want it to print "Hello world!", before all other messages, you do:xt.checkPremove("hello_world",40)
This creates a new stack in the stackgroup, if it didn't exist earlier, called "hello_world", with priority 40.Priority is a number, that the higher it is, the earlier the stack will be called.
If a stack had priority 50, it would be called even before yours, but most stacks should have priority 0. In fact, if no priority is defined, it will default to 0.
Now, the stack is there, but it has no contents, and as such, it will just be ignored. To add a printing function:
xt.pushPremove("hello_world",function()print("Hello world!")end)
You can try this out by calling any movement function. Movement functions call the premove stack before moving.Handlers are usually passed arguments. The move/turn functions pass arguments to listeners.
Premove and Forcemove(wait, I will explain) pass the direction the move was done. This can be "fw", "bk", "up" or "dn".
Postmove also passes the direction, but passes a second argument, whether the move was correctly done or not.
Preturn and Postturn pass the direction of the turn, either "r" or "l", for right or left respectively.
You can call any stackgroup by doing xt.call<stackgroupName>(arg1,arg2,arg3,…)
xt.callPreturn("r") --Will trick XT into thinking it turned right.
There are 4 more move functions: xt.ffw, xt.fbk, xt.fup, xt.fdn.
These are "force" functions. These will not fail, and will call the forcemove stackgroup until it succeeds.
For example, if it can't move forward, it will call the forcemove stackgroup, which might contain a digging function. It would then try again, and maybe it could move this time.
xt.fpushForcemove("main",xt.digDir)
xt.fpush<stackgroupName> is a handy function that will check the stack, and then push it. You can supply a third, default priority argument.xt.digDir is also a handy function, that receives a direction argument ("fw", "bk", "up" or "dn") and digs in that direction.
It also turns out that the argument the move functions passes is exactly those, so it works in any direction.
In fact, xt.forceDig (a function to pass as a forcemove handler) is just xt.digDir renamed.
There is also xt.placeDir, xt.detectDir, and xt.***Dir functions for any turtle action, even moves, under the name "goDir" and "fgoDir"
You are encouraged to use this system for things like digging 3 blocks at a time or digging your way through.
Just do things like this:
xt.fpushForcemove("main",function()
xt.digUp()
xt.digDn()
xt.dig()
end)
xt.ffw(45) --Force through 45 blocks
xt.popForcemove("main")
What xt.reloadStacks does is clear the stackgroups, so previous modifications by other programs get reset, and add the position saving/updating.
xt.reloadStacks has 2 optional arguments:
xt.reloadStacks(dontAutosave,checkTurns)
dontAutoSave will disable position updating. This will stop hammering your HDD/SSD, but you will not be able to check the position.checkTurns will enable usage of xt.shouldReorient(). When a turtle is turning and it is shut down, xt.shouldReorient() will return true.
This should not be useful, but just in case, it's there.
If you read the short documentation, you should know that xt.setTurtleUser calls xt.reloadStacks
Before calling xt.reloadStacks, calling any move/turn function will error.
The current position is saved in xt.x, xt.y, xt.z and xt.f
X, Y and Z are kinda self-explanatory, and F is the direction you are facing. A number 0-3, according to F3's facing numbers:
South: 0
West: 1
North: 2
East: 3
The coordinate system is the same as the one used in minecraft, so Y is height.These 4 values are saved, so the next time your script is run, those values will be kept.
Extra turtle features:
Auto-orientation
Calling xt.orientDigging() will try its best to get the current facing, using a torch and solid blocks.This will try to break blocks, so it's best used in mining programs. It will learn which blocks a torch can't be placed on, so in the feature it doesn't try them again.
You can add a block where a torch can't be placed on by using xt.setCantOrientOn(<inspected block or item>)
Inspect functions (Kinda important)
xt.inspect() returns just the inspected block, instead of true and the inspected block.If it fails, it will return false and the reason. Turtle functions are left unchanged.
Position management and navigation
There are a few functions for going/facing/navigating anywhere.Orientation:
xt.fc is a table that contains shortcuts for orientation values:
xt.fc.south = 0
xt.fc.west = 1
xt.fc.north = 2
xt.fc.east = 3
xt.fc.zplus = xt.fc["z+"] = 0
xt.fc.xminus = xt.fc["x-"] = 1
xt.fc.zminus = xt.fc["z-"] = 2
xt.fc.xplus = xt.fc["x+"] = 3
xt.fc[0] = "south"
xt.fc[1] = "west"
xt.fc[2] = "north"
xt.fc[3] = "east"
xt.face faces the turtle a certain direction:
xt.face(xt.fc.east)
Gotos:
xt.gotoX, xt.gotoY and xt.gotoZ will do what you expect them to do:
They receive 1 argument and go to that specific coordinate.
xt.gotoXZ is a little bit different:
It receives two arguments (X and Z) and tries to go there with the minimal amount of turns
xt.gotoXZY and xt.gotoYXZ definitely don't do what you expect them to do:
They both receive X, Y and Z in the same order, it's just that xt.gotoXZY handles the X and Z first and then handles the Y, while xt.gotoYXZ does the opposite.
All these have xt.fgoto*** twins, that call the respective force-functions, like xt.ffw, xt.fbk, xt.fup and xt.fdn
Navigation:
xt.navXZ(x,z) will navigate to a certain coordinate.
Navigation is traveling through the floor. The turtle will go as close to the floor as possible, and follow topography.
xt.navXZ will never fail, but it may get stuck, since in weird landscape there may be no way to get somewhere.
It is currently kind of "experimental", since I don't use it much.
Value Saving
This is the most important in computers (non-turtle)This allows you to save anything to disk in the easiest way possible.
Values are variables that persist across reboots.
First, you need to set a user. You need to set it with xt.setUser(random string). That random string has to be a user, unique to your program.
Please note that xt.setUser messes with your environment. You can look at the source to see how to avoid calling it.
If you read the short documentation, you should know that xt.setTurtleUser calls xt.setUser.
For example, you could call xt.setUser("quarry_program"), and the values you save would be unique to your program. This is to avoid name collision.
So, if some other program sets its user to "mining_program", he can't see your values, and doesn't get confused.
Once you set your user, you can add values. Calling xt.addValue(valueName,defaultValue) does this.
xt.addValue adds a value, loads it from disk, and if it does not exist, it assigns it the defaultValue.
Once a value is loaded you can access it from xt.valueName
For example, I could call xt.addValue("stage",0) and then do xt.stage=xt.stage+1
The value is automatically saved when it is set, so when doing xt.stage=3, 3 is saved to disk, so the next time your program is run, adding value "stage" will assign it 3 and not 0, which is the default value.
The value saving system uses the custom serialization system, which allows for recursive tables.
Tables, however, are not saved automatically when a value is changed.
If I have a value xt.myTable={}, doing xt.myTable.hi="hello" will not automatically save it, you would have to call xt.save("myTable") after it.
Please note that users are not any kind of encryption, so they are not secure in any way, and they can be checked on xt.values.<user>.<value name> or on the filesystem as "xtdata/<user>/<value name>
There are also global values, which can be seen by all users. You are encouraged not to use them, as they can mess with other programs, and if you do, give it a very unique name, like "quarry_program_globalvalue".
You can do them by supplying a third argument to xt.addValue, like this:
xt.addValue(valueName,defaultValue,isGlobal)
Global values "hide" user-specific values, and to access user-specific values the global value has to be deleted, with xt.deleteValue:
xt.deleteValue(valueName,isGlobal)
xt.deleteValue can delete user-specific values, even if there is a global value with the same name.
The second argument is optional. It defaults to user-specific values.
Serialization
There are 2 functions for serialization and de-serialization: xt.serialize and xt.unserialize (mind the z)These allow you to serialize most of the tables, including recursive or complicated pointer tables.
But there are are other 2 serialization functions that are even more robust: xt.fserialize and xt.funserialize (they force serialization)
These can serialize functions, metatables and even will try native functions. Just make sure functions don't use upvalues.
But what if you want to serialize metatables, but not functions? All serializing functions have 3 extra arguments:
xt.serialize(serializeFunctions,serializeMetatables,serializeNativeFunctions)
And xt.funserialize just inverts these arguments, so it would look like this:
xt.fserialize(dontSerializeFunctions,dontSerializeMetatables,dontSerializeNativeFunctions)
This works as well with unserializing functions.
Other types, like coroutines, cannot be serialized. Nil can be serialized.
General Stackgroups
The full stackgroup explanation is inside Turtle, since it is the only practical use. I have included it outside turtles since it can be used outside them.The turtle explanation is centered around turtle usage, so this is just a complementary documentation for outside-of-turtles stackgroup usage.
Doing xt.addStackgroup(stackgroupName) will add a new stackgroup, and all its push<name>, pop<name>, etc functions to the xt api.
Adding a stackgroup that already exists will clear the old one.
You can peek a certain stack inside a stackgroup using xt.peek<stackgroupName>(stackName).
Peeking will return the top element in that stack.
There is also a for iterator:
for stackname,topelement in xt.peekAll<stackgroupName>() do
print("Calling the top element for stack "..stackname)
topelement();
end
But it's recommended to just do xt.call<stackgroupName> as it's faster.Startup sessions
When you are running a persistent program, you would like it to be resumed after a reboot, but to do this you would have to deal with startup hassle.xt.enableRestoration and xt.disableRestoration solve this.
Calling xt.enableRestoration() will try to get the current program with shell.getRunningProgram() and setup a startup to run it.
If an old startup is found, it will ask the user, with print() and read(), what to do with it. It will always be restored back to its original place after.
Calling xt.disableRestoration() will delete the startup program if it is the auto-generated one and send the previous startup back, so it can be used again.
This will delete no programs outside of the auto-generated ones.
While the restoration is enabled, a "restore" file is created on the root, that can be run to disable the restoration. It will print an error message if XT is not loaded.
The "restore" program is deleted after the restoration is disabled, and it will be neither created or removed if a previous file exists on "restore".
Ideally, you would call xt.enableRestoration() at the beginning of your program and call xt.disableRestoration() when you quit.
xt.enableRestoration has two optional arguments:
xt.enableRestoration(programPath,oldStartupHandling)
The program path can be used to run another program that the shell's current program, and oldStartupHandling is used to override the question asked to the user.It can be:
"p": Run the previous startup in parallel to this program.
"b": Run the previous startup before this program.
"a": Run the previous startup after this program ends.
"i": Don't run the previous program at all.
And now, how to use it in your scripts with as least hassle as possible:
Usage in programs
To include it in your program, you have 3 alternatives:#1 Paste the downloader at the top of your script.
This is the most recommended option.
Just copy/paste the downloader at the top of your script. It uses http and prints messages, but that is configurable.
Here it is: http://pastebin.com/XJEZGHGd
If you are a space-maniac, or 157 lines is too much for you, here is a 10-line minified, configurable downloader: http://pastebin.com/L8FvhR0w
If you are a space-maniac and a security-maniac, you can minify the readable version yourself. I use https://mothereff.in/lua-minifier
#2 Paste the non-http extractor at the top of your script
In case you can't use the internet, http is blocked or anything, here is an extractor, that contains a minified version of XT inside a huge string:
http://pastebin.com/8ji5UD5h
#3 Download XT separately and pack it with your program as an external file
This is very bad if you want your program to be single-file (which is very useful) but can also be good if your program is multi-file
This, however, will keep your XT version outdated, so it's discouraged. You better pack the downloader and run it.
To load XT this way, just run it using shell.run or dofile.
Todo list
-Add equipment tracking-Make non-recursive serialization?
-Improve navigation
-Fix native function serialization in odd circumstances
You don't have to credit me in any way, just don't claim the API itself is yours. Otherwise, you can use it, modify it or distribute it without any credit or asking.
If you do redistribute it, please don't remove the first comment in the file.