Creating chainable functions that manipulate strings
#1
I'm working on a bunch of plugin message related things, and it occurred to me that having a chainable set of functions would be great. I have functions for reading and writing UTFs (strings), Bytes (8 bit numbers), Shorts (16 bit numbers), Ints (32...), and Longs (64..)*.

Currently the "write" functions return that section of the "string" that will eventually form a message. So, for example, to send a BungeeCord Connect plugin message the code would be:
cPlayer:GetClientHandle():SendPluginMessage("BungeeCord",writeUTF("Connect") .. writeUTF("Server Name"))
What I would like is to be able to do it like this:
cPlayer:GetClientHandle():SendPluginMessage("BungeeCord",writeUTF("Connect"):writeUTF("Server Name"))
or at least:
cPlayer:GetClientHandle():SendPluginMessage("BungeeCord","":writeUTF("Connect"):writeUTF("Server Name"))
Is there a way I could do that? I tried:
function writeUTF(self,a_in) --String
  assert(type(self) == "string", "Not a string!")
  if a_in then
    assert(type(a_in) == "string", "Not a string!")
    local len = a_in:len()
    return self .. string.char((len-127)/256,len % 256) .. a_in
  else
    local len = self:len()
    return string.char((len-127)/256,len % 256) .. self
  end
end
and the same thing but with self replaced with a_str. I didn't EXPECT those to work as self is supposed to be an object (itself haha?), but I was really hoping it would work. How do the string functions do it? My google foo was not enough to get anything more than manuals on what they do in the code, not behind the scenes.

I have "read" functions for the same datatypes*. Currently, they return the value they read and the part of the "message" string that remains after removing the translated section. So
readUTF("\0\5Hello\0\2Hi")
returns
"Hello", "\0\2Hi"
How could I make THESE chainable? It may be easier as I can return an array instead of the actual value, so that instead of "Hello", "\0\2Hi", the above would return {1="Hello"},"\0\2Hi", then chaining onto that result, the output would be {1="Hello",2="Hi"},"" and that's the end of that.

Finally, I want to make a writeSmartInteg(#) that identifies the smallest number type that # can be stored in and stores it as such and readSmartInteg(string) that figures out what type of number is being presented based on the first two bytes (\0\1 is always byte, \0\2 short, etc) and returns the number.

To make those chainable, I should just have to format it the same way and have it carefully call the right read/write functions. It don't think it would be difficult to do this, but then again I have no idea how hard chaining functions that act on strings will be so.. yeah.



*I originally planned on including read/writeFloat, read/writeDouble, and read/writeUTF16, but those proved an absolutely massive hassle. I'll likely get around to the Float and Double eventually, but I doubt I'll ever work on UTF16.
Reply
Thanks given by:
#2
Try this:
PluginMsg = {}

function PluginMsg:new(a_Channel)
	local obj = {}
	setmetatable(obj, PluginMsg)
	self.__index = self
	
	obj.Channel = a_Channel
	obj.Msg = ""
	
	return obj
end

function PluginMsg:WriteUTF(a_In)
	assert(type(a_In) == "string", "Not a string!")
	local len = a_In:len()
	self.Msg = self.Msg .. string.char((len-127)/256,len % 256) .. a_In
	
	return self
end


function PluginMsg:Get()
	return self.Channel, self.Msg
end


-- How to use:
local Obj = PluginMsg:new("BungeeCord")
:WriteUTF("Connect")
:WriteUTF("MyAmazingServer")

Player:GetClientHandle():SendPluginMessage(Obj:Get())

Now, I haven't tested this, so it might not even load.
Reply
Thanks given by:
#3
Aside from my copy/paste capitalizing the "o" in line 8 (wtf?), it compiled fine.

Adding this to a test file with your code:
function readUTF(a_str)
  local len, str = string.byte(a_str,1) * 256 + string.byte(a_str,2), a_str:sub(3)
  return str:sub(1,len), str:sub(len+1)
end
local obj = PluginMsg:new("Bungeecord"):WriteUTF("Test"):WriteUTF("Hi")
local channel, msg = obj:Get()
local o1, rem1 = readUTF(msg)
local o2, _ = readUTF(rem1)
print(o1 .. " " .. o2)
I got the error "{file location stuff}\testchaining.lua:31: attempt to call method 'WriteUTF' (a nil value)" where 31 is the line where I defined obj. I then tried splitting it up
local obj = PluginMsg:new("Bungeecord")
local obj2 = obj:WriteUTF("Test")
local obj3 = obj2:WriteUTF("Hi")
but it errored out (same error) on the first line with WriteUTF. I'm assuming this means that somethings going wrong in the metatable line, but I've no idea how to fix it.
Reply
Thanks given by:
#4
The capitalized 'o' is because I made a typo. I corrected it, but you already copied it Wink I also forgot to add "self.__index = self". Place that after the setmetatable function and it should work.

I've already adjusted and tested my example. It should work fine now Smile
Reply
Thanks given by:
#5
Haha at the typosTongue

Thanks to your help, I've now got a library of read and write functions for plugin messages! Going to do some testing to make sure everything is coded right, but it look s promising!
Reply
Thanks given by:
#6
Currently working on writeFloat and readFloat functions. Read is working perfectly (THAT TOOK SO MUCH WORK). Write is kinda broken. Doubles will come next. The method for doubles should be identical, just with more work (I've got to calculate the first FOUR bits of a byte instead of the first bit, but otherwise it's identical with bigger numbers).

Everything else, however, is functional. This includes a BungeeCord plugin message library that can handle every type of bungee plugin message EXCEPT Forward and ForwardToPlayer. I need a LOT more information on those two than is provided in the plugin message tutorial on spigotmc. Mainly I need to see how the message gets thrown into the string once Cuberite receives it. But I can't do that without first sending the message, and I can't do THAT without learning Java, something I really don't want to do right now.
Reply
Thanks given by:
#7
I think this would be much better suited as a C++ implementation exported to Lua. It's far easier to do that bit twiddling in C++. To be honest, I'd have no idea how to read and write floats and doubles in Lua only, considering that the Lua's number type can internally be an integer, float or double, based on compile-time settings.
Reply
Thanks given by:
#8
(10-28-2015, 07:48 PM)xoft Wrote: I think this would be much better suited as a C++ implementation exported to Lua. It's far easier to do that bit twiddling in C++. To be honest, I'd have no idea how to read and write floats and doubles in Lua only, considering that the Lua's number type can internally be an integer, float or double, based on compile-time settings.

Oh it most certainly would be. IIRC it's something like 4 lines of C to do the conversion and one line to push to lua. Part of this is me wanting to get better at programming. Part of it is a challenge. And now, part of it is proving it's possibleTongue

But the basic idea is you use string.char and string.byte with some careful math to produce manipulation of individual bits within the four byte sequence.

Edit: And it's done! I'm still deeming float and double manipulation as experimental, as they seem to only retain accuracy to ~2 decimal places between translations (something that could be fixed with the C++ implementation as C++ has proper bit/byte manipulation instead of the rather large number of math.floor I used), but I've now got a plugin message library that can read and write Strings, Bytes, Shorts, Ints, Longs, Bools, Floats, and Doubles in a chainable manner!

For reference: To convert between the bytestring received with plugin messages and a float, you take the first 4 bytes of the bytestring and change their definition from a string to a float. Literally that's it. I don't think it's that simple to code, but that is the process you'd need to emulate to get it into C++. I've seen some code snippets where they use byte unions to do this. For Doubles, it's exactly the same but with 8 bytes. The other direction is literally just that. \63\128\0\0 is the binary representation of the float number 1. The string is literally composed of \63\128\0\0.

If you/someone else were to give us a Lua function that "calls" the C++ function, I'd be even happier than I am now.
Reply
Thanks given by:




Users browsing this thread: 7 Guest(s)