mirror of
https://github.com/MihailRis/voxelcore.git
synced 2026-10-04 10:31:50 +00:00
Merge pull request #847 from MihailRis/client-side-tests
client-side `--script`, `--test` support (part 1)
This commit is contained in:
commit
4295af15cc
55 changed files with 1660 additions and 590 deletions
|
|
@ -19,7 +19,7 @@ local function update_line(line, uptime)
|
|||
end
|
||||
end
|
||||
|
||||
events.on("core:chat", function(message)
|
||||
local core_chat_handler = events.on("core:chat", function(message)
|
||||
while #lines >= max_lines do
|
||||
document[lines[1][1]]:destruct()
|
||||
table.remove(lines, 1)
|
||||
|
|
@ -36,7 +36,7 @@ end)
|
|||
function on_open()
|
||||
if not initialized then
|
||||
initialized = true
|
||||
|
||||
|
||||
document.root:setInterval(1/animation_fps * 1000, function ()
|
||||
local uptime = time.uptime()
|
||||
for _, line in ipairs(lines) do
|
||||
|
|
@ -52,3 +52,7 @@ function on_open()
|
|||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function on_destroy()
|
||||
events.remove("core:chat", core_chat_handler)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -148,3 +148,29 @@ function error(message, level)
|
|||
end
|
||||
__error(message, level)
|
||||
end
|
||||
|
||||
local LoggerClass = {
|
||||
__index = {
|
||||
info = function (self, text)
|
||||
debug.log(string.format("[%s] %s", self.name, text))
|
||||
end,
|
||||
warning = function (self, text)
|
||||
debug.warning(string.format("[%s] %s", self.name, text))
|
||||
end,
|
||||
error = function (self, text)
|
||||
debug.error(string.format("[%s] %s", self.name, text))
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
local _getinfo = debug.getinfo
|
||||
|
||||
function debug.Logger(name)
|
||||
if not name then
|
||||
local source = _getinfo(2, "S").source
|
||||
name = file.remove_ext(source)
|
||||
else
|
||||
name = string.format("%s:%s", file.prefix(_getinfo(2, "S").source), name)
|
||||
end
|
||||
return setmetatable({name=name}, LoggerClass)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ function events.on(event, func)
|
|||
events.handlers[event] = {}
|
||||
end
|
||||
table.insert(events.handlers[event], func)
|
||||
return func
|
||||
end
|
||||
|
||||
function events.reset(event, func)
|
||||
|
|
@ -24,6 +25,14 @@ function events.reset(event, func)
|
|||
end
|
||||
end
|
||||
|
||||
function events.remove(event, handler)
|
||||
local handlers = events.handlers[event]
|
||||
if not handlers then
|
||||
return
|
||||
end
|
||||
table.remove_value(handlers, handler)
|
||||
end
|
||||
|
||||
function events.remove_by_prefix(prefix)
|
||||
for name, handlers in pairs(events.handlers) do
|
||||
local actualname = name
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ function file.parent(path)
|
|||
return dir
|
||||
end
|
||||
|
||||
function file.remove_ext(path)
|
||||
return string.format("%s/%s", file.parent(path), file.stem(path))
|
||||
end
|
||||
|
||||
function file.path(path)
|
||||
local pos = path:find(':')
|
||||
return path:sub(pos + 1)
|
||||
|
|
|
|||
|
|
@ -59,11 +59,7 @@ end
|
|||
|
||||
function table.extend(t1, t2)
|
||||
for i, v in pairs(t2) do
|
||||
if type(i) == "number" then
|
||||
t1[#t1 + 1] = v
|
||||
else
|
||||
t1[i] = v
|
||||
end
|
||||
t1[i] = v
|
||||
end
|
||||
|
||||
return t1
|
||||
|
|
@ -201,3 +197,11 @@ function table.tostring(t)
|
|||
end
|
||||
return s..']'
|
||||
end
|
||||
|
||||
function table.keys(t)
|
||||
local keys = {}
|
||||
for key, _ in pairs(t) do
|
||||
table.insert(keys, key)
|
||||
end
|
||||
return keys
|
||||
end
|
||||
|
|
|
|||
|
|
@ -77,15 +77,18 @@ function Element.new(docname, name)
|
|||
end
|
||||
|
||||
-- the engine automatically creates an instance for every ui document (layout)
|
||||
local Document = {}
|
||||
function Document.new(docname)
|
||||
return setmetatable({name=docname}, {
|
||||
__index=function(self, k)
|
||||
local elem = Element.new(self.name, k)
|
||||
rawset(self, k, elem)
|
||||
return elem
|
||||
local Document = {
|
||||
__index=function(self, k)
|
||||
if type(k) ~= "string" then
|
||||
error("element id is not a string")
|
||||
end
|
||||
})
|
||||
local elem = Element.new(self.name, k)
|
||||
rawset(self, k, elem)
|
||||
return elem
|
||||
end
|
||||
}
|
||||
function Document.new(docname)
|
||||
return setmetatable({name=docname}, Document)
|
||||
end
|
||||
|
||||
local RadioGroup = {}
|
||||
|
|
|
|||
131
res/modules/internal/stream_providers/bytearray.lua
Normal file
131
res/modules/internal/stream_providers/bytearray.lua
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
local io_stream = require "core:io_stream"
|
||||
|
||||
local lib = { }
|
||||
|
||||
local buffers = { }
|
||||
local positions = { }
|
||||
|
||||
local next_descriptor = 0
|
||||
|
||||
local function open_descriptor(buffer)
|
||||
next_descriptor = next_descriptor + 1
|
||||
|
||||
buffers[next_descriptor] = buffer
|
||||
positions[next_descriptor] = 1
|
||||
|
||||
return next_descriptor
|
||||
end
|
||||
|
||||
local function require_descriptor(descriptor)
|
||||
if not buffers[descriptor] then
|
||||
error("unknown descriptor")
|
||||
end
|
||||
end
|
||||
|
||||
function lib.read(descriptor, length)
|
||||
require_descriptor(descriptor)
|
||||
|
||||
local buf = buffers[descriptor]
|
||||
local buf_length = #buf
|
||||
local pos = positions[descriptor]
|
||||
|
||||
local to_read = math.min(buf_length - pos + 1, length)
|
||||
|
||||
if to_read <= 0 then
|
||||
return Bytearray()
|
||||
end
|
||||
|
||||
local segment = buf:slice(pos, to_read)
|
||||
|
||||
positions[descriptor] = pos + to_read
|
||||
|
||||
return segment
|
||||
end
|
||||
|
||||
function lib.write(descriptor, data)
|
||||
require_descriptor(descriptor)
|
||||
|
||||
local buf = buffers[descriptor]
|
||||
local pos = positions[descriptor]
|
||||
|
||||
local buf_length = #buf
|
||||
local data_length = #data
|
||||
|
||||
local end_pos = pos + data_length - 1
|
||||
|
||||
-- size ensuring
|
||||
if end_pos > buf_length then
|
||||
for i = buf_length + 1, end_pos do
|
||||
buf[i] = 0
|
||||
end
|
||||
end
|
||||
|
||||
for i = 1, data_length do
|
||||
buf[i + pos - 1] = data[i]
|
||||
end
|
||||
|
||||
positions[descriptor] = pos + data_length
|
||||
end
|
||||
|
||||
function lib.seek(descriptor, mode, offset)
|
||||
require_descriptor(descriptor)
|
||||
|
||||
local buf = buffers[descriptor]
|
||||
local buf_length = #buf
|
||||
|
||||
local base
|
||||
|
||||
if mode == 'b' then
|
||||
base = 1
|
||||
elseif mode == 'c' then
|
||||
base = positions[descriptor]
|
||||
elseif mode == 'e' then
|
||||
base = buf_length + 1
|
||||
else error('invalid seek mode') end
|
||||
|
||||
local new_pos = base + offset
|
||||
|
||||
if new_pos < 1 then
|
||||
error('failed to seek stream')
|
||||
end
|
||||
|
||||
positions[descriptor] = new_pos
|
||||
end
|
||||
|
||||
function lib.tell(descriptor)
|
||||
require_descriptor(descriptor)
|
||||
|
||||
return positions[descriptor]
|
||||
end
|
||||
|
||||
function lib.available(descriptor)
|
||||
require_descriptor(descriptor)
|
||||
|
||||
local buf = buffers[descriptor]
|
||||
local pos = positions[descriptor]
|
||||
|
||||
return math.max(#buf - pos + 1, 0)
|
||||
end
|
||||
|
||||
function lib.is_alive(descriptor)
|
||||
return buffers[descriptor] ~= nil
|
||||
end
|
||||
|
||||
function lib.close(descriptor)
|
||||
require_descriptor(descriptor)
|
||||
|
||||
buffers[descriptor] = nil
|
||||
positions[descriptor] = nil
|
||||
end
|
||||
|
||||
return function(buffer, binary_mode)
|
||||
if binary_mode == nil then
|
||||
binary_mode = true
|
||||
end
|
||||
|
||||
return io_stream.new(
|
||||
open_descriptor(buffer),
|
||||
binary_mode,
|
||||
lib
|
||||
)
|
||||
end
|
||||
|
|
@ -4,15 +4,28 @@ local lib = {
|
|||
read = file.__read_descriptor,
|
||||
write = file.__write_descriptor,
|
||||
seek = file.__seek_descriptor,
|
||||
tell = file.__tell_descriptor,
|
||||
flush = file.__flush_descriptor,
|
||||
available = file.__available_descriptor,
|
||||
is_alive = file.__has_descriptor,
|
||||
close = file.__close_descriptor
|
||||
}
|
||||
|
||||
local __open_descriptor = file.__open_descriptor
|
||||
file.__open_descriptor = nil
|
||||
file.__read_descriptor = nil
|
||||
file.__write_descriptor = nil
|
||||
file.__seek_descriptor = nil
|
||||
file.__tell_descriptor = nil
|
||||
file.__flush_descriptor = nil
|
||||
file.__available_descriptor = nil
|
||||
file.__has_descriptor = nil
|
||||
file.__close_descriptor = nil
|
||||
|
||||
return function(path, mode)
|
||||
return io_stream.new(
|
||||
file.__open_descriptor(path, mode),
|
||||
__open_descriptor(path, mode),
|
||||
mode:find('b') ~= nil,
|
||||
lib
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ int close(int fd);
|
|||
ssize_t read(int fd, void *buf, size_t count);
|
||||
ssize_t write(int fd, const void *buf, size_t count);
|
||||
int fcntl(int fd, int cmd, ...);
|
||||
int ioctl(int fd, unsigned long request, ...);
|
||||
|
||||
const char *strerror(int errnum);
|
||||
]]
|
||||
|
|
@ -20,6 +21,7 @@ local O_WRONLY = 0x1
|
|||
local O_RDWR = 0x2
|
||||
local O_NONBLOCK = 0x800
|
||||
local F_GETFL = 3
|
||||
local FIONREAD = 0x541B
|
||||
|
||||
local function getError()
|
||||
local err = FFI.errno()
|
||||
|
|
@ -58,12 +60,16 @@ function lib.write(fd, bytearray)
|
|||
end
|
||||
end
|
||||
|
||||
function lib.seek(fd, mode, offset)
|
||||
error("cannot seek the named pipe")
|
||||
end
|
||||
function lib.available(fd)
|
||||
if fd == nil or fd < 0 then return 0 end
|
||||
|
||||
function lib.flush(fd)
|
||||
-- no flush on unix
|
||||
local bytes_ready = FFI.new("int[1]", 0)
|
||||
|
||||
if C.ioctl(fd, FIONREAD, bytes_ready) == -1 then
|
||||
return 0
|
||||
end
|
||||
|
||||
return tonumber(bytes_ready[0])
|
||||
end
|
||||
|
||||
function lib.is_alive(fd)
|
||||
|
|
|
|||
|
|
@ -97,14 +97,26 @@ function lib.write(handle, bytearray)
|
|||
end
|
||||
end
|
||||
|
||||
function lib.seek(handle, mode, offset)
|
||||
error("cannot seek the named pipe")
|
||||
end
|
||||
|
||||
function lib.flush(handle)
|
||||
C.FlushFileBuffers(handle)
|
||||
end
|
||||
|
||||
function lib.available(handle)
|
||||
if handle == nil or handle == INVALID_HANDLE_VALUE then
|
||||
return 0
|
||||
end
|
||||
|
||||
local bytes_available = FFI.new("DWORD[1]")
|
||||
|
||||
local success = C.PeekNamedPipe(handle, nil, 0, nil, bytes_available, nil)
|
||||
|
||||
if success == 0 then
|
||||
return 0
|
||||
end
|
||||
|
||||
return tonumber(bytes_available[0])
|
||||
end
|
||||
|
||||
function lib.is_alive(handle)
|
||||
if handle == nil or handle == INVALID_HANDLE_VALUE then
|
||||
return false
|
||||
|
|
|
|||
64
res/modules/internal/stream_providers/socket.lua
Normal file
64
res/modules/internal/stream_providers/socket.lua
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
local io_stream = require "core:io_stream"
|
||||
|
||||
local lib = { }
|
||||
|
||||
local sockets = { }
|
||||
|
||||
local next_descriptor = 0
|
||||
|
||||
local function open_descriptor(socket)
|
||||
next_descriptor = next_descriptor + 1
|
||||
|
||||
sockets[next_descriptor] = socket
|
||||
|
||||
return next_descriptor
|
||||
end
|
||||
|
||||
local function require_descriptor(descriptor)
|
||||
if not sockets[descriptor] then
|
||||
error("unknown descriptor")
|
||||
end
|
||||
end
|
||||
|
||||
function lib.read(descriptor, length)
|
||||
require_descriptor(descriptor)
|
||||
|
||||
return sockets[descriptor]:recv(length)
|
||||
end
|
||||
|
||||
function lib.write(descriptor, data)
|
||||
require_descriptor(descriptor)
|
||||
|
||||
sockets[descriptor]:send(data)
|
||||
end
|
||||
|
||||
function lib.available(descriptor)
|
||||
require_descriptor(descriptor)
|
||||
|
||||
return sockets[descriptor]:available()
|
||||
end
|
||||
|
||||
function lib.is_alive(descriptor)
|
||||
local socket = sockets[descriptor]
|
||||
|
||||
return socket ~= nil and socket:is_alive()
|
||||
end
|
||||
|
||||
function lib.close(descriptor)
|
||||
require_descriptor(descriptor)
|
||||
|
||||
sockets[descriptor]:close()
|
||||
sockets[descriptor] = nil
|
||||
end
|
||||
|
||||
return function(socket, binary_mode)
|
||||
if binary_mode == nil then
|
||||
binary_mode = true
|
||||
end
|
||||
|
||||
return io_stream.new(
|
||||
open_descriptor(socket),
|
||||
binary_mode,
|
||||
lib
|
||||
)
|
||||
end
|
||||
139
res/modules/internal/test.lua
Normal file
139
res/modules/internal/test.lua
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
local __app = __vc_app
|
||||
|
||||
local function get_node_center(node)
|
||||
local pos = node.wpos
|
||||
local size = node.size
|
||||
return pos[1] + size[1] / 2, pos[2] + size[2] / 2
|
||||
end
|
||||
|
||||
local function types_check_enum(value, enum)
|
||||
if value == nil then
|
||||
return true, nil
|
||||
end
|
||||
if type(value) ~= "string" then
|
||||
return false, string.format("string expected [%s]", table.concat(table.keys(enum), "|"))
|
||||
end
|
||||
return true, nil
|
||||
end
|
||||
|
||||
local function types_check_number(value)
|
||||
if value == nil then
|
||||
return true, nil
|
||||
end
|
||||
local value_type = type(value)
|
||||
if value_type ~= "number" then
|
||||
return false, string.format("number expected, got %s", value_type)
|
||||
end
|
||||
end
|
||||
|
||||
local function check_option(t, key, func, ...)
|
||||
local value = t[key]
|
||||
if value == nil then
|
||||
return nil
|
||||
end
|
||||
local success, message = func(value, ...)
|
||||
if not success then
|
||||
error(string.format("invalid option '%s': %s", key, message))
|
||||
end
|
||||
end
|
||||
|
||||
local buttons_enum = {
|
||||
left = 0,
|
||||
right = 1,
|
||||
middle = 2,
|
||||
}
|
||||
local no_options = {}
|
||||
|
||||
local _tick = __app.tick
|
||||
local _set_button_pressed = test.set_button_pressed
|
||||
local _set_key_pressed = test.set_key_pressed
|
||||
local _enter_text = test.enter_text
|
||||
test.set_button_pressed = nil
|
||||
test.set_key_pressed = nil
|
||||
test.enter_text = nil
|
||||
|
||||
function test.click(node, options)
|
||||
options = options or no_options
|
||||
|
||||
check_option(options, 'button', types_check_enum, buttons_enum)
|
||||
check_option(options, 'timeout', types_check_number)
|
||||
|
||||
local button = buttons_enum[options.button or 'left']
|
||||
local timeout = options.timeout or 1000
|
||||
|
||||
local tm = time.precise_time()
|
||||
while (time.precise_time() - tm) * 1000 < timeout do
|
||||
if node.exists then
|
||||
break
|
||||
end
|
||||
_tick()
|
||||
end
|
||||
if not node.exists then
|
||||
error(string.format("timeout %s ms exceeded: element %s does not exists",
|
||||
math.floor(timeout), string.escape(rawget(node, 'name'))))
|
||||
end
|
||||
|
||||
local center_x, center_y = get_node_center(node)
|
||||
_set_button_pressed(button, center_x, center_y, true)
|
||||
_tick()
|
||||
_set_button_pressed(button, center_x, center_y, false)
|
||||
_tick()
|
||||
end
|
||||
|
||||
-- TODO: replace with resolving in C++ side
|
||||
function test.find_by_attr(node, attr, value)
|
||||
if getmetatable(node) == Document then
|
||||
return test.find_by_attr(node.root, attr, value)
|
||||
end
|
||||
if node[attr] == value then
|
||||
return node
|
||||
end
|
||||
local i = 1
|
||||
while true do
|
||||
local subnode = node[i]
|
||||
if subnode == nil then
|
||||
break
|
||||
end
|
||||
local found = test.find_by_attr(subnode, attr, value)
|
||||
if found then
|
||||
return found
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
function test.find_by_text(node, text)
|
||||
return test.find_by_attr(node, "text", text)
|
||||
end
|
||||
|
||||
function test.find_by_attr_presence(node, attr)
|
||||
if node[attr] then
|
||||
return node
|
||||
end
|
||||
local i = 1
|
||||
while true do
|
||||
local subnode = node[i]
|
||||
if subnode == nil then
|
||||
break
|
||||
end
|
||||
local found = test.find_by_attr_presence(subnode, attr)
|
||||
if found then
|
||||
return found
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
function test.fill(node, text, options)
|
||||
test.click(node, options)
|
||||
__app.tick()
|
||||
_enter_text(text)
|
||||
test.press("enter")
|
||||
end
|
||||
|
||||
function test.press(key)
|
||||
_set_key_pressed(key, true)
|
||||
__app.tick()
|
||||
_set_key_pressed(key, false)
|
||||
__app.tick()
|
||||
end
|
||||
|
|
@ -25,13 +25,13 @@ local ALL_FLUSH_MODES = {
|
|||
local CR = string.byte('\r')
|
||||
local LF = string.byte('\n')
|
||||
|
||||
local function readFully(result, readFunc)
|
||||
local function read_fully(result, read_func)
|
||||
local isTable = type(result) == "table"
|
||||
|
||||
local buf
|
||||
|
||||
repeat
|
||||
buf = readFunc(MAX_BUFFER_SIZE)
|
||||
buf = read_func(MAX_BUFFER_SIZE)
|
||||
|
||||
if isTable then
|
||||
for i = 1, #buf do
|
||||
|
|
@ -42,41 +42,46 @@ local function readFully(result, readFunc)
|
|||
end
|
||||
|
||||
--[[
|
||||
|
||||
descriptor - descriptor of stream for provided I/O library
|
||||
binaryMode - if enabled, most methods will expect bytes instead of strings
|
||||
ioLib - I/O library. Should include the following functions:
|
||||
read(descriptor: int, length: int) -> Bytearray
|
||||
May return bytearray with a smaller size if bytes have not arrived yet or have run out
|
||||
May throw error if descriptor isn't readable
|
||||
write(descriptor: int, data: Bytearray)
|
||||
flush(descriptor: int)
|
||||
May throw error if descriptor isn't writeable
|
||||
[optional] seek(descriptor: int, mode: string, offset: int)
|
||||
Mode may be 'b' (relative begin), 'c' (relative current), 'e' (relative end + 1)
|
||||
[optional] tell(descriptor: int) -> int
|
||||
[optional] flush(descriptor: int)
|
||||
[optional] available(descriptor: int) -> int
|
||||
is_alive(descriptor: int) -> bool
|
||||
close(descriptor: int)
|
||||
--]]
|
||||
|
||||
function io_stream.new(descriptor, binaryMode, ioLib, mode, flushMode)
|
||||
function io_stream.new(descriptor, binary_mode, io_lib, mode, flush_mode)
|
||||
mode = mode or DEFAULT_MODE
|
||||
flushMode = flushMode or FLUSH_MODE_ALL
|
||||
flush_mode = flush_mode or FLUSH_MODE_ALL
|
||||
|
||||
local self = setmetatable({}, io_stream)
|
||||
|
||||
self.descriptor = descriptor
|
||||
self.binaryMode = binaryMode
|
||||
self.maxBufferSize = MAX_BUFFER_SIZE
|
||||
self.ioLib = ioLib
|
||||
self.binary_mode = binary_mode
|
||||
self.max_buffer_size = MAX_BUFFER_SIZE
|
||||
self.io_lib = io_lib
|
||||
|
||||
self:set_mode(mode)
|
||||
self:set_flush_mode(flushMode)
|
||||
self:set_flush_mode(flush_mode)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function io_stream:is_binary_mode()
|
||||
return self.binaryMode
|
||||
return self.binary_mode
|
||||
end
|
||||
|
||||
function io_stream:set_binary_mode(binaryMode)
|
||||
self.binaryMode = binaryMode ~= nil
|
||||
function io_stream:set_binary_mode(binary_mode)
|
||||
self.binary_mode = binary_mode ~= nil
|
||||
end
|
||||
|
||||
function io_stream:get_mode()
|
||||
|
|
@ -88,124 +93,141 @@ function io_stream:set_mode(mode)
|
|||
error("invalid stream mode: "..mode)
|
||||
end
|
||||
|
||||
if self.mode == BUFFERED_MODE then
|
||||
self.writeBuffer:clear()
|
||||
self.readBuffer:clear()
|
||||
if self.write_buffer then
|
||||
self.write_buffer:clear()
|
||||
self.write_buffer = nil
|
||||
end
|
||||
|
||||
if mode == BUFFERED_MODE and not self.writeBuffer then
|
||||
self.writeBuffer = Bytearray()
|
||||
self.readBuffer = Bytearray()
|
||||
if self.read_buffer then
|
||||
self.read_buffer:clear()
|
||||
self.read_buffer = nil
|
||||
end
|
||||
|
||||
if mode == BUFFERED_MODE then
|
||||
self.write_buffer = Bytearray(self.max_buffer_size)
|
||||
self.read_buffer = Bytearray(self.max_buffer_size)
|
||||
end
|
||||
|
||||
self.mode = mode
|
||||
end
|
||||
|
||||
function io_stream:get_flush_mode()
|
||||
return self.flushMode
|
||||
return self.flush_mode
|
||||
end
|
||||
|
||||
function io_stream:set_flush_mode(flushMode)
|
||||
if not table.has(ALL_FLUSH_MODES, flushMode) then
|
||||
error("invalid flush mode: "..flushMode)
|
||||
function io_stream:set_flush_mode(flush_mode)
|
||||
if not table.has(ALL_FLUSH_MODES, flush_mode) then
|
||||
error("invalid flush mode: " .. flush_mode)
|
||||
end
|
||||
|
||||
self.flushMode = flushMode
|
||||
self.flush_mode = flush_mode
|
||||
end
|
||||
|
||||
function io_stream:get_max_buffer_size()
|
||||
return self.maxBufferSize
|
||||
return self.max_buffer_size
|
||||
end
|
||||
|
||||
function io_stream:set_max_buffer_size(maxBufferSize)
|
||||
self.maxBufferSize = maxBufferSize
|
||||
function io_stream:set_max_buffer_size(max_buffer_size)
|
||||
self.max_buffer_size = max_buffer_size
|
||||
|
||||
self.write_buffer = Bytearray(self.max_buffer_size)
|
||||
self.read_buffer = Bytearray(self.max_buffer_size)
|
||||
end
|
||||
|
||||
function io_stream:available(length)
|
||||
local available = self.io_lib.available and self.io_lib.available(self.descriptor) or 0
|
||||
|
||||
if self.mode == BUFFERED_MODE then
|
||||
self:__update_read_buffer()
|
||||
available = available + #self.read_buffer
|
||||
end
|
||||
|
||||
if not length then
|
||||
return #self.readBuffer
|
||||
else
|
||||
return #self.readBuffer >= length
|
||||
end
|
||||
if not length then
|
||||
return available
|
||||
else
|
||||
return available >= length
|
||||
end
|
||||
end
|
||||
|
||||
function io_stream:__update_read_buffer()
|
||||
local readed = Bytearray()
|
||||
|
||||
readFully(readed, function(length) return self.ioLib.read(self.descriptor, length) end)
|
||||
|
||||
self.readBuffer:append(readed)
|
||||
|
||||
if #self.readBuffer > self.maxBufferSize then
|
||||
error "buffer overflow"
|
||||
end
|
||||
end
|
||||
|
||||
function io_stream:__read(length)
|
||||
function io_stream:__read(length, from_read_fully)
|
||||
if self.mode == YIELD_MODE then
|
||||
if from_read_fully then
|
||||
return self.io_lib.read(self.descriptor, length)
|
||||
end
|
||||
|
||||
local buffer = Bytearray()
|
||||
|
||||
while #buffer < length do
|
||||
buffer:append(self.ioLib.read(self.descriptor, length - #buffer))
|
||||
buffer:append(self.io_lib.read(self.descriptor, length - #buffer))
|
||||
|
||||
if #buffer < length then coroutine.yield() end
|
||||
end
|
||||
|
||||
return buffer
|
||||
elseif self.mode == BUFFERED_MODE then
|
||||
self:__update_read_buffer()
|
||||
local buf_len = #self.read_buffer
|
||||
|
||||
if #self.readBuffer < length then
|
||||
error "buffer underflow"
|
||||
if buf_len < length then
|
||||
self.read_buffer:append(
|
||||
self.io_lib.read(self.descriptor, self.max_buffer_size - buf_len)
|
||||
)
|
||||
end
|
||||
|
||||
local copy
|
||||
buf_len = #self.read_buffer
|
||||
|
||||
if #self.readBuffer == length then
|
||||
copy = Bytearray()
|
||||
|
||||
copy:append(self.readBuffer)
|
||||
length = math.min(buf_len, length)
|
||||
|
||||
self.readBuffer:clear()
|
||||
local copy = self.read_buffer:slice(1, length)
|
||||
|
||||
if buf_len == length then
|
||||
self.read_buffer:clear()
|
||||
else
|
||||
copy = Bytearray()
|
||||
|
||||
for i = 1, length do
|
||||
copy[i] = self.readBuffer[i]
|
||||
end
|
||||
|
||||
self.readBuffer:remove(1, length)
|
||||
self.read_buffer:remove(1, length)
|
||||
end
|
||||
|
||||
return copy
|
||||
elseif self.mode == DEFAULT_MODE then
|
||||
return self.ioLib.read(self.descriptor, length)
|
||||
return self.io_lib.read(self.descriptor, length)
|
||||
end
|
||||
end
|
||||
|
||||
function io_stream:__write(data)
|
||||
if self.mode == BUFFERED_MODE then
|
||||
self.writeBuffer:append(data)
|
||||
local data_length = #data
|
||||
|
||||
if #self.writeBuffer > self.maxBufferSize then
|
||||
error "buffer overflow"
|
||||
if #self.write_buffer + data_length > self.max_buffer_size then
|
||||
self:flush()
|
||||
end
|
||||
|
||||
if data_length > self.max_buffer_size then
|
||||
local to_write = math.floor(data_length / self.max_buffer_size) * self.max_buffer_size
|
||||
local to_save = data_length - to_write
|
||||
|
||||
self.io_lib.write(self.descriptor, data:slice(1, to_write))
|
||||
|
||||
self:flush()
|
||||
|
||||
self.write_buffer = data:slice(to_write + 1, to_save)
|
||||
else self.write_buffer:append(data) end
|
||||
elseif self.mode == DEFAULT_MODE or self.mode == YIELD_MODE then
|
||||
return self.ioLib.write(self.descriptor, data)
|
||||
return self.io_lib.write(self.descriptor, data)
|
||||
end
|
||||
end
|
||||
|
||||
function io_stream:read_fully(useTable)
|
||||
if self.binaryMode then
|
||||
local result = useTable and Bytearray() or { }
|
||||
function io_stream:read_fully(use_table)
|
||||
if self.binary_mode then
|
||||
local result = use_table and { } or Bytearray()
|
||||
|
||||
readFully(result, function() return self:__read(self.maxBufferSize) end)
|
||||
local avail = self:available()
|
||||
|
||||
if avail == 0 then
|
||||
avail = self.max_buffer_size
|
||||
end
|
||||
|
||||
read_fully(result, function() return self:__read(avail, true) end)
|
||||
|
||||
return result
|
||||
else
|
||||
if useTable then
|
||||
if use_table then
|
||||
local lines = { }
|
||||
|
||||
local line
|
||||
|
|
@ -220,7 +242,7 @@ function io_stream:read_fully(useTable)
|
|||
else
|
||||
local result = Bytearray()
|
||||
|
||||
readFully(result, function() return self:__read(self.maxBufferSize) end)
|
||||
read_fully(result, function() return self:__read(self.max_buffer_size) end)
|
||||
|
||||
return utf8.tostring(result)
|
||||
end
|
||||
|
|
@ -262,57 +284,61 @@ function io_stream:write_line(str)
|
|||
self:__write(utf8.tobytes(str .. "\n"))
|
||||
end
|
||||
|
||||
function io_stream:read(arg, useTable)
|
||||
local argType = type(arg)
|
||||
function io_stream:read(arg, use_table)
|
||||
local arg_type = type(arg)
|
||||
|
||||
if self.binaryMode then
|
||||
local byteArr
|
||||
if self.binary_mode then
|
||||
local byte_arr
|
||||
|
||||
if argType == "number" then
|
||||
if arg_type == "number" then
|
||||
-- using 'arg' as length
|
||||
|
||||
byteArr = self:__read(arg)
|
||||
byte_arr = self:__read(arg)
|
||||
|
||||
if useTable == true then
|
||||
if use_table == true then
|
||||
local t = { }
|
||||
|
||||
for i = 1, #byteArr do
|
||||
t[i] = byteArr[i]
|
||||
for i = 1, #byte_arr do
|
||||
t[i] = byte_arr[i]
|
||||
end
|
||||
|
||||
return t
|
||||
else
|
||||
return byteArr
|
||||
return byte_arr
|
||||
end
|
||||
elseif argType == "string" then
|
||||
elseif arg_type == "string" then
|
||||
return byteutil.unpack(
|
||||
arg,
|
||||
self:__read(byteutil.get_size(arg))
|
||||
)
|
||||
elseif argType == nil then
|
||||
elseif arg_type == "nil" then
|
||||
error(
|
||||
"in binary mode the first argument must be a string data format"..
|
||||
" for the library \"byteutil\" or the number of bytes to read"
|
||||
)
|
||||
else
|
||||
error("unknown argument type: "..argType)
|
||||
error("unknown argument type: "..arg_type)
|
||||
end
|
||||
else
|
||||
if not arg then
|
||||
return self:read_line()
|
||||
else
|
||||
local linesCount = arg
|
||||
local trimLastEmptyLines = useTable or true
|
||||
local lines_count = arg
|
||||
local trim_last_empty_lines = use_table
|
||||
|
||||
if linesCount < 0 then error "count of lines to read must be positive" end
|
||||
if use_table == nil then
|
||||
trim_last_empty_lines = true
|
||||
end
|
||||
|
||||
if lines_count < 0 then error "count of lines to read must be positive" end
|
||||
|
||||
local result = { }
|
||||
|
||||
for i = 1, linesCount do
|
||||
for i = 1, lines_count do
|
||||
result[i] = self:read_line()
|
||||
end
|
||||
|
||||
if trimLastEmptyLines then
|
||||
if trim_last_empty_lines then
|
||||
local i = #result
|
||||
|
||||
while i >= 0 do
|
||||
|
|
@ -340,41 +366,53 @@ function io_stream:read(arg, useTable)
|
|||
end
|
||||
|
||||
function io_stream:write(arg, ...)
|
||||
local argType = type(arg)
|
||||
local arg_type = type(arg)
|
||||
|
||||
if self.binaryMode then
|
||||
local byteArr
|
||||
if self.binary_mode then
|
||||
local byte_arr
|
||||
|
||||
if argType ~= "string" then
|
||||
if arg_type ~= "string" then
|
||||
-- using arg as bytes table/bytearray
|
||||
|
||||
if argType == "table" then
|
||||
byteArr = Bytearray(arg)
|
||||
if arg_type == "table" then
|
||||
byte_arr = Bytearray(arg)
|
||||
else
|
||||
byteArr = arg
|
||||
byte_arr = arg
|
||||
end
|
||||
else
|
||||
byteArr = byteutil.pack(arg, ...)
|
||||
byte_arr = byteutil.pack(arg, ...)
|
||||
end
|
||||
|
||||
self:__write(byteArr)
|
||||
self:__write(byte_arr)
|
||||
else
|
||||
if argType == "string" then
|
||||
if arg_type == "string" then
|
||||
self:write_line(arg)
|
||||
elseif argType == "table" then
|
||||
elseif arg_type == "table" then
|
||||
for i = 1, #arg do
|
||||
self:write_line(arg[i])
|
||||
end
|
||||
else error("unknown argument type: "..argType) end
|
||||
else error("unknown argument type: "..arg_type) end
|
||||
end
|
||||
end
|
||||
|
||||
function io_stream:seek(mode, offset)
|
||||
self.ioLib.seek(self.descriptor, mode, offset)
|
||||
if not self.io_lib.seek then
|
||||
error("cannot seek this stream")
|
||||
end
|
||||
|
||||
self.io_lib.seek(self.descriptor, mode, offset)
|
||||
end
|
||||
|
||||
function io_stream:tell()
|
||||
if not self.io_lib.tell then
|
||||
error("cannot tell this stream")
|
||||
end
|
||||
|
||||
return self.io_lib.tell(self.descriptor)
|
||||
end
|
||||
|
||||
function io_stream:is_alive()
|
||||
return self.ioLib.is_alive(self.descriptor)
|
||||
return self.io_lib.is_alive(self.descriptor)
|
||||
end
|
||||
|
||||
function io_stream:is_closed()
|
||||
|
|
@ -383,20 +421,24 @@ end
|
|||
|
||||
function io_stream:close()
|
||||
if self.mode == BUFFERED_MODE then
|
||||
self.readBuffer:clear()
|
||||
self.writeBuffer:clear()
|
||||
self.read_buffer:clear()
|
||||
self.write_buffer:clear()
|
||||
end
|
||||
|
||||
return self.ioLib.close(self.descriptor)
|
||||
return self.io_lib.close(self.descriptor)
|
||||
end
|
||||
|
||||
function io_stream:flush()
|
||||
if self.mode == BUFFERED_MODE and #self.writeBuffer > 0 then
|
||||
self.ioLib.write(self.descriptor, self.writeBuffer)
|
||||
self.writeBuffer:clear()
|
||||
if self.mode == BUFFERED_MODE and #self.write_buffer > 0 then
|
||||
self.io_lib.write(self.descriptor, self.write_buffer)
|
||||
self.write_buffer:clear()
|
||||
end
|
||||
|
||||
if self.flushMode ~= FLUSH_MODE_ONLY_BUFFER then self.ioLib.flush(self.descriptor) end
|
||||
if self.flush_mode ~= FLUSH_MODE_ONLY_BUFFER then
|
||||
if self.io_lib.flush then
|
||||
self.io_lib.flush(self.descriptor)
|
||||
elseif self:is_closed() then error("stream is closed") end
|
||||
end
|
||||
end
|
||||
|
||||
return io_stream
|
||||
|
|
@ -49,6 +49,7 @@ local Socket = {__index={
|
|||
end
|
||||
return self:recv(length, usetable)
|
||||
end,
|
||||
as_stream=network.__as_stream,
|
||||
close=function(self) return network.__close(self.id) end,
|
||||
available=function(self) return network.__available(self.id) or 0 end,
|
||||
is_alive=function(self) return network.__is_alive(self.id) end,
|
||||
|
|
|
|||
|
|
@ -127,6 +127,10 @@ require "core:internal/extensions/inventory"
|
|||
asserts = require "core:internal/asserts"
|
||||
events = require "core:internal/events"
|
||||
|
||||
if test then
|
||||
require "core:internal/test"
|
||||
end
|
||||
|
||||
function pack.unload(prefix)
|
||||
events.remove_by_prefix(prefix)
|
||||
end
|
||||
|
|
@ -334,6 +338,10 @@ else
|
|||
os.pid = ffi.C.getpid()
|
||||
end
|
||||
|
||||
require("core:io_stream").wrap_bytearray = require "core:internal/stream_providers/bytearray"
|
||||
|
||||
network.__as_stream = require "core:internal/stream_providers/socket"
|
||||
|
||||
math.randomseed(time.uptime() * 1536227939)
|
||||
|
||||
rules = require "core:internal/rules"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue