Merge branch 'environments' into refactor-world-renderer

This commit is contained in:
MihailRis 2026-07-26 23:56:40 +03:00
commit 53a9d47905
127 changed files with 3090 additions and 870 deletions

View file

@ -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

View file

@ -11,7 +11,7 @@
@World generator
</button>
<button id='content_btn' onclick='save_state() menu.page="content"'/>
<button onclick='create_world()' margin='1,20,1,1'>
<button onclick='create_world()' margin='0,20,0,1'>
@Create World
</button>
<button onclick='menu:back()'>@Back</button>

View file

@ -1,8 +1,77 @@
local this = {}
local app = __vc_app
function this.equals(expected, fact)
local function diff_canvas(expected, fact)
this.equals(expected.width, fact.width, "image width")
this.equals(expected.width, fact.width, "image height")
local diff = Canvas({expected.width, expected.height})
local mismatches = 0
for y = 0, expected.height - 1 do
for x = 0, expected.width - 1 do
local ca = expected:at(x, y)
local cb = fact:at(x, y)
if ca == cb then
diff:set(x, y, ca)
else
diff:set(x, y, 0xff0000ff)
mismatches = mismatches + 1
end
end
end
return diff, mismatches
end
local TEST_ARTIFACTS_PATH = "export:test-results"
local function get_test_artifact_path(name)
local path = TEST_ARTIFACTS_PATH
if not file.isdir(path) then
file.mkdirs(path)
end
return file.join(path, name)
end
function this.equals(expected, fact, prefix)
local mt = getmetatable(expected)
if mt == Canvas then
local diff, mismatches = diff_canvas(expected, fact)
if mismatches > 0 then
prefix = prefix or app.script or base64.encode_urlsafe(random.bytes(6))
local path_diff = get_test_artifact_path(
string.format("%s.diff.png", prefix)
)
local path_fact = get_test_artifact_path(
string.format("%s.fact.png", prefix)
)
local path_expected = get_test_artifact_path(
string.format("%s.expected.png", prefix)
)
file.write_bytes(path_diff, diff:encode('png'))
file.write_bytes(path_fact, fact:encode('png'))
file.write_bytes(path_expected, expected:encode('png'))
local total_pixels = expected.width * expected.height
assert(mismatches == 0, string.format(
"%s pixels of %s (%s%%) are different, diff saved as %s",
mismatches,
total_pixels,
math.round(mismatches / total_pixels * 100, 2),
string.escape(path_diff))
)
end
return
end
if prefix then
prefix = prefix .. " "
else
prefix = ""
end
assert(fact == expected, string.format(
"(fact == expected) assertion failed\n Expected: %s\n Fact: %s",
prefix .. "(fact == expected) assertion failed\n Expected: %s\n Fact: %s",
expected, fact
))
end

View file

@ -12,11 +12,30 @@ FFI.cdef[[
} bytearray_t;
]]
local malloc = FFI.C.malloc
local free = FFI.C.free
local FFIBytearray
local bytearray_type
local allocated_bytes = 0
local GC_THRESHOLD = 50 * 1024 * 1024
local function malloc(size)
local raw = FFI.C.malloc(size)
if raw == nil then
return nil
end
allocated_bytes = allocated_bytes + size
if allocated_bytes >= GC_THRESHOLD then
collectgarbage("step", 200)
allocated_bytes = 0
end
return raw
end
local function grow_buffer(self, elems)
local new_capacity = math.ceil(self.capacity / 0.75 + elems)
local prev = self.bytes

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -35,3 +35,89 @@ function math.sum(...)
return sum
end
function math.sign(x)
return (x > 0) and 1 or (x < 0 and -1 or 0)
end
local noise_period = 3021
local noise_rands = {}
for i=1,noise_period do
table.insert(noise_rands, math.random() * 2.0 - 1.0)
end
local function smoothstep(t)
return t * t * (3 - 2 * t)
end
local function sample_noise(x)
x = x % noise_period
local left_cell = math.floor(x)
local right_cell = (left_cell + 1) % noise_period
local t = smoothstep(x - left_cell)
return noise_rands[left_cell + 1] * (1.0 - t)
+ noise_rands[right_cell + 1] * t
end
function math.noise(x, octaves)
x = math.abs(x)
octaves = octaves or 1
local value = 0.0
local mul = 1.0
for i=1,octaves do
value = value * (1.0 - mul) + sample_noise(x + 37 * i) * mul
x = x * 2.0
mul = mul * 0.5
end
return value
end
local function lerp(a, b, t)
return a + (b - a) * t
end
local function rand_2d(x, y)
return noise_rands[math.floor(x * 317 + y) % noise_period + 1]
end
local function sample_noise2d(x, y)
x = math.abs(x)
y = math.abs(y)
local x0 = math.floor(x)
local y0 = math.floor(y)
local x1 = x0 + 1
local y1 = y0 + 1
local tx = x - x0
local ty = y - y0
local sx = smoothstep(tx)
local sy = smoothstep(ty)
local c00 = rand_2d(x0, y0)
local c10 = rand_2d(x1, y0)
local c01 = rand_2d(x0, y1)
local c11 = rand_2d(x1, y1)
local bottom = lerp(c00, c10, sx)
local top = lerp(c01, c11, sx)
return lerp(bottom, top, sy)
end
function math.noise2d(x, y, octaves)
octaves = octaves or 1
local value = 0.0
local mul = 1.0
for i=1,octaves do
value = value * (1.0 - mul)
+ sample_noise2d(x + 37 * i, y + 73 * i) * mul
x = x * 2.0
y = y * 2.0
mul = mul * 0.5
end
return value
end

View file

@ -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

View file

@ -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 = {}

View 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

View file

@ -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

View file

@ -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)

View file

@ -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

View 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

View 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

View file

@ -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

View file

@ -5,4 +5,25 @@ function util.create_demo_world(generator)
app.new_world("demo", "2019", generator or "core:default")
end
function util.check_vec3(expected, fact, name, delta)
delta = delta or 1e-4
for i=1,3 do
assert(math.abs(fact[i] - expected[i]) < delta,
name.."["..i.."] expected: "..expected[i]..", fact: "..fact[i])
end
end
function util.check_float(expected, fact, name, delta)
delta = delta or 1e-4
local actual_delta = math.abs(fact - expected)
assert(actual_delta < delta,
name.." expected: "..expected..", fact: "..fact..
" (delta: "..actual_delta.." > "..delta..")")
end
function util.check_int(expected, fact, name)
assert(fact == expected,
name.." expected: "..expected..", fact: "..fact)
end
return util

View file

@ -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,

View file

@ -142,14 +142,16 @@ end
function is_flight() return flight end
function set_flight(flag) flight = flag end
function set_flight(flag)
flight = flag
body:set_gravity_scale(flight and 0.0 or props.gravity_scale)
end
local prev_angle = (vec2.angle({dir[3], dir[1]})) % 360
function on_physics_update(delta)
local grounded = body:is_grounded()
body:set_vdamping(flight)
body:set_gravity_scale(flight and 0.0 or props.gravity_scale)
body:set_linear_damping(
(flight or not grounded) and props.air_damping or props.ground_damping
)

View file

@ -58,16 +58,12 @@ local function process_player_inputs(pid, rot, delta)
end
end
local prepared = false
body:set_enabled(false)
mob.set_flight(true)
function on_player_set(pid)
mob.set_flight(player.is_flight(pid))
body:set_body_type(player.is_noclip(pid) and "kinematic" or "dynamic")
end
function on_physics_update(delta)
if not prepared then
prepared = true
body:set_enabled(true)
end
local pid = entity:get_player()
if pid == -1 then
return

View file

@ -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
@ -312,6 +316,8 @@ entities.get_all = function(uids)
return stdcomp.get_all(uids)
end
end
world.raycast = entities.__world_raycast
entities.__world_raycast = nil
__vc_scripts_registry = require "core:internal/scripts_registry"
@ -332,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"