mirror of
https://github.com/MihailRis/voxelcore.git
synced 2026-10-04 18:41:51 +00:00
Merge remote-tracking branch 'upstream/client-side-tests' into io-streams-enhance
# Conflicts: # src/logic/scripting/descriptors_manager.cpp # src/logic/scripting/descriptors_manager.hpp
This commit is contained in:
commit
db014cca09
46 changed files with 1172 additions and 282 deletions
34
dev/tests/io_stream.lua
Normal file
34
dev/tests/io_stream.lua
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
local device = file.create_memory_device()
|
||||||
|
file.write(device..":test.txt", "Hello\nWorld")
|
||||||
|
file.write_bytes(device..":test.bin", Bytearray({20, 30, 100, 200, 255}))
|
||||||
|
|
||||||
|
local stream = file.open(device..":test.txt", 'r')
|
||||||
|
|
||||||
|
local line1 = stream:read_line()
|
||||||
|
local line2 = stream:read_line()
|
||||||
|
|
||||||
|
assert(line1 == "Hello")
|
||||||
|
assert(line2 == "World")
|
||||||
|
|
||||||
|
local stream2 = file.open(device..":test.bin", 'rb')
|
||||||
|
local data = stream2:read(5)
|
||||||
|
assert(data[1] == 20)
|
||||||
|
assert(data[2] == 30)
|
||||||
|
assert(data[3] == 100)
|
||||||
|
assert(data[4] == 200)
|
||||||
|
assert(data[5] == 255)
|
||||||
|
|
||||||
|
local stream3 = file.open(device..":test.txt", 'r')
|
||||||
|
assert(stream3:is_alive())
|
||||||
|
stream3:close()
|
||||||
|
assert(not stream3:is_alive())
|
||||||
|
|
||||||
|
local stream4 = file.open(device..":test.txt", 'r')
|
||||||
|
stream4:seek('b', 6)
|
||||||
|
local line = stream4:read_line()
|
||||||
|
assert(line == "World")
|
||||||
|
|
||||||
|
local stream5 = file.open(device..":test.txt", 'r')
|
||||||
|
stream5:seek('e', 0)
|
||||||
|
local pos = stream5:tell()
|
||||||
|
assert(pos == 11)
|
||||||
|
|
@ -42,6 +42,7 @@ Subsections:
|
||||||
- [utf8](scripting/builtins/libutf8.md)
|
- [utf8](scripting/builtins/libutf8.md)
|
||||||
- [vec2, vec3, vec4](scripting/builtins/libvecn.md)
|
- [vec2, vec3, vec4](scripting/builtins/libvecn.md)
|
||||||
- [world](scripting/builtins/libworld.md)
|
- [world](scripting/builtins/libworld.md)
|
||||||
|
- [Extensions for standard libraries](scripting/extensions.md)
|
||||||
- [Module core:bit_converter](scripting/modules/core_bit_converter.md)
|
- [Module core:bit_converter](scripting/modules/core_bit_converter.md)
|
||||||
- [Module core:data_buffer](scripting/modules/core_data_buffer.md)
|
- [Module core:data_buffer](scripting/modules/core_data_buffer.md)
|
||||||
- [Module core:vector2, core:vector3](scripting/modules/core_vector2_vector3.md)
|
- [Module core:vector2, core:vector3](scripting/modules/core_vector2_vector3.md)
|
||||||
|
|
|
||||||
204
doc/en/scripting/extensions.md
Normal file
204
doc/en/scripting/extensions.md
Normal file
|
|
@ -0,0 +1,204 @@
|
||||||
|
# Standard Library Extensions
|
||||||
|
|
||||||
|
The **stdmin.lua** kernel script defines functions that extend and complement some of the standard **Lua** libraries.
|
||||||
|
|
||||||
|
## Contents:
|
||||||
|
- [table extensions](#table-extensions)
|
||||||
|
- [string extensions](#string-extensions)
|
||||||
|
- [math extensions](#math-extensions)
|
||||||
|
- [bit extensions](#bit-extensions)
|
||||||
|
- [additional global functions](#additional-global-functions)
|
||||||
|
|
||||||
|
## Table Extensions
|
||||||
|
```lua
|
||||||
|
-- Creates and returns a copy of the given table by creating a new one and copying all elements from the given table into it.
|
||||||
|
table.copy(t: table) -> table
|
||||||
|
|
||||||
|
-- The deep copy function creates a full copy of the source table, including all its subtables.
|
||||||
|
table.deep_copy(t: table) -> table
|
||||||
|
|
||||||
|
-- Returns the number of pairs in the given table.
|
||||||
|
table.count_pairs(t: table) -> int
|
||||||
|
|
||||||
|
-- Returns one element from the given table at a random position.
|
||||||
|
table.random(t: table) -> any
|
||||||
|
|
||||||
|
-- Returns true if x is contained in t.
|
||||||
|
table.has(t: table, x: any) -> boolean
|
||||||
|
|
||||||
|
-- Returns the index of x in t. If the given object is not contained in the table, the function returns -1.
|
||||||
|
table.index(t: table, x: any) -> int
|
||||||
|
|
||||||
|
-- Removes element x from t.
|
||||||
|
table.remove_value(t: table, x: any)
|
||||||
|
|
||||||
|
-- Shuffles values in the table.
|
||||||
|
table.shuffle(t: table) -> table
|
||||||
|
|
||||||
|
-- Adds values from table t2 to table t1. If table t2 contains a key from t1, the key's value will not be changed.
|
||||||
|
table.merge(t1: table, t2: table) -> table
|
||||||
|
|
||||||
|
-- Iterates through the table and applies a func to all its elements, returning the new value of the element.
|
||||||
|
table.map(t: table, func: function(indx, value)) -> table
|
||||||
|
|
||||||
|
-- Iterates through the table using a func that returns true if the element should be kept and false if it should be deleted.
|
||||||
|
table.filter(t: table, func: function(indx, value)) -> table
|
||||||
|
|
||||||
|
-- Allows you to safely retrieve the value for the specified key. If the key exists in the table, the method will return its value.
|
||||||
|
-- If the key is missing, the method will set it to the default value and return it.
|
||||||
|
table.set_default(t: table, key: int | string, default: any) -> any
|
||||||
|
|
||||||
|
-- Returns a "flattened" version of the original table.
|
||||||
|
table.flat(t: table) -> table
|
||||||
|
|
||||||
|
-- Returns a deep "flat" version of the original table.
|
||||||
|
table.deep_flat(t: table) -> table
|
||||||
|
|
||||||
|
-- Returns a truncated version of the table from index start to index stop, inclusive. Key-value pairs
|
||||||
|
-- are not preserved in the new table. For nil values, the value starts at 1 and ends at #arr, respectively.
|
||||||
|
table.sub(arr: table, start: number | nil, stop: number | nil) -> table
|
||||||
|
|
||||||
|
-- Adds a value to the table only if it was not originally there.
|
||||||
|
table.insert_unique(t: table, val: any)
|
||||||
|
table.insert_unique(t: table, pos: int, val: any)
|
||||||
|
|
||||||
|
-- Returns a table containing all keys of the given table, including numeric ones. table.keys(t: table) -> table
|
||||||
|
|
||||||
|
-- Adds all key-value pairs from table extension to table t. If a key from t is present in extension, its value will be overwritten.
|
||||||
|
table.extend(t: table, extension: table) -> table
|
||||||
|
|
||||||
|
-- Converts the passed table to a string.
|
||||||
|
table.tostring(t: table) -> string
|
||||||
|
```
|
||||||
|
|
||||||
|
## String extensions
|
||||||
|
|
||||||
|
It's important to note that all of the functions listed below that extend **string** can be used as meta-methods on string instances, i.e.:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local str = "ABA str BAB"
|
||||||
|
|
||||||
|
if str:starts_with("ABA") and str:ends_with("BAB") then
|
||||||
|
print(str:replace("BA", "DC"))
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- Splits the string into parts based on the specified separator/expression and returns the result as a table of strings. If withpattern is true, the separator parameter will be evaluated as a regular expression. string.explode(separator: string, str: string, withpattern: boolean) -> table<string>
|
||||||
|
|
||||||
|
-- Splits the string into parts based on the specified delimiter and returns the result as a table of strings.
|
||||||
|
string.split(str: string, delimiter: string) -> table<string>
|
||||||
|
|
||||||
|
-- Escapes special characters in the string, such as `()[]+-.$%^?*`, into `%character` format. The `NUL` character (`\0`) will be converted to `%z`.
|
||||||
|
string.pattern_safe(str: string) -> string
|
||||||
|
|
||||||
|
-- Splits seconds into hours, minutes, and milliseconds and formats them using the following parameter order: `minutes, seconds, milliseconds`, and then returns the result. If format is not specified, it returns a table where:
|
||||||
|
-- h - hours,
|
||||||
|
-- m - minutes,
|
||||||
|
-- s - seconds,
|
||||||
|
-- ms - milliseconds.
|
||||||
|
string.formatted_time(seconds: number, format: string) -> string | table
|
||||||
|
|
||||||
|
-- Replaces all substrings in str equal to tofind with toreplace and returns a string with all the modified substrings.
|
||||||
|
string.replace(str: string, tofind: string, toreplace: string) -> string
|
||||||
|
|
||||||
|
-- Removes all characters equal to char from the string from the left and right ends and returns the result.
|
||||||
|
-- If the char parameter is undefined, all empty characters will be selected.
|
||||||
|
string.trim(str: string, char: string) -> string
|
||||||
|
|
||||||
|
-- Removes all characters equal to char from the string from the left end and returns the result.
|
||||||
|
-- If the char parameter is undefined, all empty characters will be selected.
|
||||||
|
string.trim_left(str: string, char: string) -> string
|
||||||
|
|
||||||
|
-- Removes all characters equal to char from the right end of the string and returns the result.
|
||||||
|
-- If the char parameter is undefined, all empty characters will be selected.
|
||||||
|
string.trim_right(str: string, char: string) -> string
|
||||||
|
|
||||||
|
-- Returns true if the string begins with the substring start.
|
||||||
|
string.starts_with(str: string, start: string) -> boolean
|
||||||
|
|
||||||
|
-- Returns true if the string ends with the substring endStr.
|
||||||
|
string.ends_with(str: string, endStr: string) -> boolean
|
||||||
|
|
||||||
|
-- The string.lower and string.upper functions are also overridden by utf8.lower and utf8.upper.
|
||||||
|
|
||||||
|
-- Escapes a string. It is an alias for utf8.escape.
|
||||||
|
string.escape(str: string) -> string
|
||||||
|
|
||||||
|
-- Escapes special XML characters. An alias for utf8.escape_xml.
|
||||||
|
string.escape_xml(text: string) -> string
|
||||||
|
|
||||||
|
-- Adds a char to the left and right of the string until its size equals size.
|
||||||
|
-- By default, char is equal to the space character.
|
||||||
|
string.pad(str: string, size: int, char: string) -> string
|
||||||
|
|
||||||
|
-- Adds a char to the left of the string until its size equals size.
|
||||||
|
-- By default, char is equal to the space character.
|
||||||
|
string.left_pad(str: string, size: int, char: string) -> string
|
||||||
|
|
||||||
|
-- Adds a char to the right of the string until its size equals size.
|
||||||
|
-- By default, char is equal to the space character.
|
||||||
|
string.right_pad(str: string, size: int, char: string) -> string
|
||||||
|
```
|
||||||
|
|
||||||
|
## Math extensions
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- Returns _in if it is in the range low <= _in <= high
|
||||||
|
-- Otherwise, returns the boundary to which _in is closest.
|
||||||
|
math.clamp(_in: number, low: number, high: number) -> number
|
||||||
|
|
||||||
|
-- Returns a random fractional number in the range low to high.
|
||||||
|
math.rand(low: number, high: number) -> number
|
||||||
|
|
||||||
|
-- Returns the normalized value of num relative to conf.
|
||||||
|
math.normalize(num: number, [optional] conf: number) -> number
|
||||||
|
|
||||||
|
-- Returns the rounded value of num to the specified number of decimal places.
|
||||||
|
math.round(num: number, [optional] places: number) -> number
|
||||||
|
|
||||||
|
-- Returns the sum of all received arguments. If a table was passed as an argument, the method will return the sum of all its elements.
|
||||||
|
math.sum(x: number, ... | t: table) -> number
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bit extensions
|
||||||
|
```lua
|
||||||
|
|
||||||
|
-- Common arguments:
|
||||||
|
-- * expr: A string containing a bitwise expression, conforming to the Lua 5.3 bitwise operations format
|
||||||
|
-- * args: A list of names of the expression arguments. If nil, the list is automatically generated based on the detected identifiers.
|
||||||
|
|
||||||
|
- Compiles the function to perform bitwise operations
|
||||||
|
-- * asFunction: If true, returns the function; otherwise, returns a string of function code
|
||||||
|
bit.compile(expr: string, args: table | nil, asFunction: boolean=true) -> function | string
|
||||||
|
|
||||||
|
-- Compiles the function to perform bitwise operations and executes it in place
|
||||||
|
-- * ...: Values to be passed to the compiled function. bit.execute(expr: string, args: table | nil, ...) -> number
|
||||||
|
```
|
||||||
|
## Additional Global Functions
|
||||||
|
|
||||||
|
This script also defines other global functions that are available for use. Their list is below.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- Returns true if the passed table is an array, that is, if each key is an integer greater than or equal to one
|
||||||
|
-- and if each key follows the previous one.
|
||||||
|
is_array(x: table) -> boolean
|
||||||
|
|
||||||
|
-- Splits the path into two parts and returns them: the entry point and the file path.
|
||||||
|
parse_path(path: string) -> string, string
|
||||||
|
|
||||||
|
-- Calls the function func iters times, passing it the arguments ..., and then prints to the console the time in microseconds that has elapsed
|
||||||
|
-- since the call to timeit. timeit(iters: int, func: function, ...)
|
||||||
|
|
||||||
|
-- Causes the coroutine to pause until the number of seconds specified in timesec has elapsed.
|
||||||
|
-- The function can only be used inside a coroutine.
|
||||||
|
sleep(timesec: number)
|
||||||
|
|
||||||
|
-- Waits for the passed coroutine to complete, returning the control flow. The function can only be used inside a coroutine.
|
||||||
|
-- Returns values similar to those returned by pcall.
|
||||||
|
await(co: coroutine) -> result, error
|
||||||
|
|
||||||
|
-- A constant storing the PID of the current engine instance.
|
||||||
|
os.pid -> number
|
||||||
|
```
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
- [расширения для string](#расширения-для-string)
|
- [расширения для string](#расширения-для-string)
|
||||||
- [расширения для math](#расширения-для-math)
|
- [расширения для math](#расширения-для-math)
|
||||||
- [расширения для bit](#расширения-для-bit)
|
- [расширения для bit](#расширения-для-bit)
|
||||||
- [Дополнительные глобальные функции](#дополнительные-глобальные-функции)
|
- [дополнительные глобальные функции](#дополнительные-глобальные-функции)
|
||||||
|
|
||||||
## Расширения для table
|
## Расширения для table
|
||||||
```lua
|
```lua
|
||||||
|
|
@ -62,6 +62,12 @@ table.sub(arr: table, start: number | nil, stop: number | nil) -> table
|
||||||
table.insert_unique(t: table, val: any)
|
table.insert_unique(t: table, val: any)
|
||||||
table.insert_unique(t: table, pos: int, val: any)
|
table.insert_unique(t: table, pos: int, val: any)
|
||||||
|
|
||||||
|
-- Возвращает таблицу, содержащую все ключи переданной таблицы, включая числовые.
|
||||||
|
table.keys(t: table) -> table
|
||||||
|
|
||||||
|
-- Добавляет в таблицу t все пары ключ-значение из таблицы extension, при этом если в extension присутствует ключ из t, то его значение будет перезаписано.
|
||||||
|
table.extend(t: table, extension: table) -> table
|
||||||
|
|
||||||
-- Конвертирует переданную таблицу в строку.
|
-- Конвертирует переданную таблицу в строку.
|
||||||
table.tostring(t: table) -> string
|
table.tostring(t: table) -> string
|
||||||
```
|
```
|
||||||
|
|
@ -200,4 +206,4 @@ await(co: coroutine) -> result, error
|
||||||
|
|
||||||
-- Константа, в которой хранится PID текущего инстанса движка.
|
-- Константа, в которой хранится PID текущего инстанса движка.
|
||||||
os.pid -> number
|
os.pid -> number
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ local function update_line(line, uptime)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
events.on("core:chat", function(message)
|
local core_chat_handler = events.on("core:chat", function(message)
|
||||||
while #lines >= max_lines do
|
while #lines >= max_lines do
|
||||||
document[lines[1][1]]:destruct()
|
document[lines[1][1]]:destruct()
|
||||||
table.remove(lines, 1)
|
table.remove(lines, 1)
|
||||||
|
|
@ -36,7 +36,7 @@ end)
|
||||||
function on_open()
|
function on_open()
|
||||||
if not initialized then
|
if not initialized then
|
||||||
initialized = true
|
initialized = true
|
||||||
|
|
||||||
document.root:setInterval(1/animation_fps * 1000, function ()
|
document.root:setInterval(1/animation_fps * 1000, function ()
|
||||||
local uptime = time.uptime()
|
local uptime = time.uptime()
|
||||||
for _, line in ipairs(lines) do
|
for _, line in ipairs(lines) do
|
||||||
|
|
@ -52,3 +52,7 @@ function on_open()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function on_destroy()
|
||||||
|
events.remove("core:chat", core_chat_handler)
|
||||||
|
end
|
||||||
|
|
|
||||||
|
|
@ -148,3 +148,29 @@ function error(message, level)
|
||||||
end
|
end
|
||||||
__error(message, level)
|
__error(message, level)
|
||||||
end
|
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] = {}
|
events.handlers[event] = {}
|
||||||
end
|
end
|
||||||
table.insert(events.handlers[event], func)
|
table.insert(events.handlers[event], func)
|
||||||
|
return func
|
||||||
end
|
end
|
||||||
|
|
||||||
function events.reset(event, func)
|
function events.reset(event, func)
|
||||||
|
|
@ -24,6 +25,14 @@ function events.reset(event, func)
|
||||||
end
|
end
|
||||||
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)
|
function events.remove_by_prefix(prefix)
|
||||||
for name, handlers in pairs(events.handlers) do
|
for name, handlers in pairs(events.handlers) do
|
||||||
local actualname = name
|
local actualname = name
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,10 @@ function file.parent(path)
|
||||||
return dir
|
return dir
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function file.remove_ext(path)
|
||||||
|
return string.format("%s/%s", file.parent(path), file.stem(path))
|
||||||
|
end
|
||||||
|
|
||||||
function file.path(path)
|
function file.path(path)
|
||||||
local pos = path:find(':')
|
local pos = path:find(':')
|
||||||
return path:sub(pos + 1)
|
return path:sub(pos + 1)
|
||||||
|
|
|
||||||
|
|
@ -59,11 +59,7 @@ end
|
||||||
|
|
||||||
function table.extend(t1, t2)
|
function table.extend(t1, t2)
|
||||||
for i, v in pairs(t2) do
|
for i, v in pairs(t2) do
|
||||||
if type(i) == "number" then
|
t1[i] = v
|
||||||
t1[#t1 + 1] = v
|
|
||||||
else
|
|
||||||
t1[i] = v
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return t1
|
return t1
|
||||||
|
|
@ -201,3 +197,11 @@ function table.tostring(t)
|
||||||
end
|
end
|
||||||
return s..']'
|
return s..']'
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
-- the engine automatically creates an instance for every ui document (layout)
|
-- the engine automatically creates an instance for every ui document (layout)
|
||||||
local Document = {}
|
local Document = {
|
||||||
function Document.new(docname)
|
__index=function(self, k)
|
||||||
return setmetatable({name=docname}, {
|
if type(k) ~= "string" then
|
||||||
__index=function(self, k)
|
error("element id is not a string")
|
||||||
local elem = Element.new(self.name, k)
|
|
||||||
rawset(self, k, elem)
|
|
||||||
return elem
|
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
local RadioGroup = {}
|
local RadioGroup = {}
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,27 @@ local lib = {
|
||||||
read = file.__read_descriptor,
|
read = file.__read_descriptor,
|
||||||
write = file.__write_descriptor,
|
write = file.__write_descriptor,
|
||||||
seek = file.__seek_descriptor,
|
seek = file.__seek_descriptor,
|
||||||
|
tell = file.__tell_descriptor,
|
||||||
flush = file.__flush_descriptor,
|
flush = file.__flush_descriptor,
|
||||||
available = file.__available_descriptor,
|
available = file.__available_descriptor,
|
||||||
is_alive = file.__has_descriptor,
|
is_alive = file.__has_descriptor,
|
||||||
close = file.__close_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.__has_descriptor = nil
|
||||||
|
file.__close_descriptor = nil
|
||||||
|
|
||||||
return function(path, mode)
|
return function(path, mode)
|
||||||
return io_stream.new(
|
return io_stream.new(
|
||||||
file.__open_descriptor(path, mode),
|
__open_descriptor(path, mode),
|
||||||
mode:find('b') ~= nil,
|
mode:find('b') ~= nil,
|
||||||
lib
|
lib
|
||||||
)
|
)
|
||||||
end
|
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
|
||||||
|
|
@ -388,6 +388,10 @@ function io_stream:seek(mode, offset)
|
||||||
self.ioLib.seek(self.descriptor, mode, offset)
|
self.ioLib.seek(self.descriptor, mode, offset)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function io_stream:tell()
|
||||||
|
return self.ioLib.tell(self.descriptor)
|
||||||
|
end
|
||||||
|
|
||||||
function io_stream:is_alive()
|
function io_stream:is_alive()
|
||||||
return self.ioLib.is_alive(self.descriptor)
|
return self.ioLib.is_alive(self.descriptor)
|
||||||
end
|
end
|
||||||
|
|
@ -414,4 +418,4 @@ function io_stream:flush()
|
||||||
if self.flushMode ~= FLUSH_MODE_ONLY_BUFFER then self.ioLib.flush(self.descriptor) end
|
if self.flushMode ~= FLUSH_MODE_ONLY_BUFFER then self.ioLib.flush(self.descriptor) end
|
||||||
end
|
end
|
||||||
|
|
||||||
return io_stream
|
return io_stream
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,10 @@ require "core:internal/extensions/inventory"
|
||||||
asserts = require "core:internal/asserts"
|
asserts = require "core:internal/asserts"
|
||||||
events = require "core:internal/events"
|
events = require "core:internal/events"
|
||||||
|
|
||||||
|
if test then
|
||||||
|
require "core:internal/test"
|
||||||
|
end
|
||||||
|
|
||||||
function pack.unload(prefix)
|
function pack.unload(prefix)
|
||||||
events.remove_by_prefix(prefix)
|
events.remove_by_prefix(prefix)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,12 @@ void audio::initialize(
|
||||||
logger.info() << "initializing NoAudio backend";
|
logger.info() << "initializing NoAudio backend";
|
||||||
backend = NoAudio::create().release();
|
backend = NoAudio::create().release();
|
||||||
}
|
}
|
||||||
|
if (settings.inputDevice.get() == "auto") {
|
||||||
|
auto inputDevices = audio::get_input_devices_names();
|
||||||
|
if (!inputDevices.empty()) {
|
||||||
|
settings.inputDevice.set(inputDevices.at(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
struct {
|
struct {
|
||||||
std::string name;
|
std::string name;
|
||||||
NumberSetting* setting;
|
NumberSetting* setting;
|
||||||
|
|
@ -210,7 +216,9 @@ void audio::initialize(
|
||||||
::input_device = backend->openInputDevice(
|
::input_device = backend->openInputDevice(
|
||||||
settings.inputDevice.get(), 44100, 1, 16
|
settings.inputDevice.get(), 44100, 1, 16
|
||||||
);
|
);
|
||||||
::input_device->startCapture();
|
if (::input_device != nullptr) {
|
||||||
|
::input_device->startCapture();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (::input_device) {
|
if (::input_device) {
|
||||||
::input_device->stopCapture();
|
::input_device->stopCapture();
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ namespace {
|
||||||
public:
|
public:
|
||||||
FTFontFile(FT_Face face, util::Buffer<ubyte> buffer)
|
FTFontFile(FT_Face face, util::Buffer<ubyte> buffer)
|
||||||
: face(std::move(face)), buffer(std::move(buffer)) {
|
: face(std::move(face)), buffer(std::move(buffer)) {
|
||||||
|
monospace = this->face->face_flags & FT_FACE_FLAG_FIXED_WIDTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
~FTFontFile() {
|
~FTFontFile() {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,12 @@ namespace vector_fonts {
|
||||||
virtual std::unique_ptr<Texture> renderPage(
|
virtual std::unique_ptr<Texture> renderPage(
|
||||||
int pageid, std::vector<Glyph>& glyphs, int size
|
int pageid, std::vector<Glyph>& glyphs, int size
|
||||||
) = 0;
|
) = 0;
|
||||||
|
|
||||||
|
bool isMonospace() const {
|
||||||
|
return monospace;
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
bool monospace = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
void initialize();
|
void initialize();
|
||||||
|
|
|
||||||
63
src/devtools/AppScriptsControl.cpp
Normal file
63
src/devtools/AppScriptsControl.cpp
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
#include "AppScriptsControl.hpp"
|
||||||
|
|
||||||
|
#include "debug/Logger.hpp"
|
||||||
|
#include "engine/CoreParameters.hpp"
|
||||||
|
#include "io/io.hpp"
|
||||||
|
#include "Project.hpp"
|
||||||
|
#include "logic/scripting/scripting.hpp"
|
||||||
|
|
||||||
|
static debug::Logger logger("app-scripts");
|
||||||
|
|
||||||
|
AppScriptsControl::AppScriptsControl(
|
||||||
|
const CoreParameters& params, const Project& project
|
||||||
|
)
|
||||||
|
: project(project) {
|
||||||
|
|
||||||
|
io::path scriptFile =
|
||||||
|
params.scriptFile.empty()
|
||||||
|
? "project:start.lua"
|
||||||
|
: std::string("script:") + params.scriptFile.filename().u8string();
|
||||||
|
if (io::exists(scriptFile)) {
|
||||||
|
logger.info() << "starting script: " << params.scriptFile.u8string();
|
||||||
|
scriptCoroutine = scripting::start_app_script(scriptFile);
|
||||||
|
} else {
|
||||||
|
logger.warning() << "script does not exists: " << params.scriptFile.u8string();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!params.headless) {
|
||||||
|
loadProjectClientScript();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppScriptsControl::loadProjectClientScript() {
|
||||||
|
io::path scriptFile = "project:project_client.lua";
|
||||||
|
if (io::exists(scriptFile)) {
|
||||||
|
logger.info() << "starting project client script: " << scriptFile.string();
|
||||||
|
clientScript = scripting::load_client_project_script(scriptFile);
|
||||||
|
} else {
|
||||||
|
logger.warning() << "project client script does not exists: " << scriptFile.string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppScriptsControl::onScreenChange(const std::string& name, bool show) {
|
||||||
|
if (clientScript) {
|
||||||
|
clientScript->onScreenChange(name, show);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppScriptsControl::tick() {
|
||||||
|
if (scriptCoroutine && scriptCoroutine->isActive()) {
|
||||||
|
scriptCoroutine->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppScriptsControl::terminate(std::string_view reason) {
|
||||||
|
if (scriptCoroutine->isActive()) {
|
||||||
|
scriptCoroutine->terminate();
|
||||||
|
logger.info() << "script has been terminated due to " << reason;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AppScriptsControl::isFinished() const {
|
||||||
|
return scriptCoroutine == nullptr || !scriptCoroutine->isActive();
|
||||||
|
}
|
||||||
30
src/devtools/AppScriptsControl.hpp
Normal file
30
src/devtools/AppScriptsControl.hpp
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "interfaces/Process.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace scripting {
|
||||||
|
class IClientProjectScript;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CoreParameters;
|
||||||
|
struct Project;
|
||||||
|
|
||||||
|
class AppScriptsControl {
|
||||||
|
public:
|
||||||
|
AppScriptsControl(const CoreParameters& params, const Project& project);
|
||||||
|
|
||||||
|
void tick();
|
||||||
|
void loadProjectClientScript();
|
||||||
|
void terminate(std::string_view reason);
|
||||||
|
|
||||||
|
void onScreenChange(const std::string& name, bool show);
|
||||||
|
|
||||||
|
bool isFinished() const;
|
||||||
|
private:
|
||||||
|
const Project& project;
|
||||||
|
std::unique_ptr<scripting::IClientProjectScript> clientScript;
|
||||||
|
std::unique_ptr<Process> scriptCoroutine;
|
||||||
|
};
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "data/dv_util.hpp"
|
#include "data/dv_util.hpp"
|
||||||
#include "debug/Logger.hpp"
|
#include "debug/Logger.hpp"
|
||||||
|
#include "engine/CoreParameters.hpp"
|
||||||
#include "io/io.hpp"
|
#include "io/io.hpp"
|
||||||
#include "io/path.hpp"
|
#include "io/path.hpp"
|
||||||
#include "logic/scripting/scripting.hpp"
|
#include "logic/scripting/scripting.hpp"
|
||||||
|
|
@ -40,26 +41,6 @@ void Project::deserialize(const dv::value& src) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Project::loadProjectClientScript() {
|
|
||||||
io::path scriptFile = "project:project_client.lua";
|
|
||||||
if (io::exists(scriptFile)) {
|
|
||||||
logger.info() << "starting project client script";
|
|
||||||
clientScript = scripting::load_client_project_script(scriptFile);
|
|
||||||
} else {
|
|
||||||
logger.warning() << "project client script does not exists";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Project::loadProjectStartScript() {
|
|
||||||
io::path scriptFile = "project:start.lua";
|
|
||||||
if (io::exists(scriptFile)) {
|
|
||||||
logger.info() << "starting project start script";
|
|
||||||
setupCoroutine = scripting::start_app_script(scriptFile);
|
|
||||||
} else {
|
|
||||||
logger.warning() << "project start script does not exists";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Permissions::has(const std::string& name) const {
|
bool Permissions::has(const std::string& name) const {
|
||||||
return permissions.find(name) != permissions.end();
|
return permissions.find(name) != permissions.end();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,12 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "interfaces/Serializable.hpp"
|
||||||
|
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "interfaces/Process.hpp"
|
|
||||||
#include "interfaces/Serializable.hpp"
|
|
||||||
|
|
||||||
namespace scripting {
|
|
||||||
class IClientProjectScript;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Permissions {
|
struct Permissions {
|
||||||
static inline std::string DEBUGGING = "debugging";
|
static inline std::string DEBUGGING = "debugging";
|
||||||
static inline std::string NETWORK = "network";
|
static inline std::string NETWORK = "network";
|
||||||
|
|
@ -27,15 +22,10 @@ struct Project : Serializable {
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string title;
|
std::string title;
|
||||||
std::vector<std::string> basePacks;
|
std::vector<std::string> basePacks;
|
||||||
std::unique_ptr<scripting::IClientProjectScript> clientScript;
|
|
||||||
std::unique_ptr<Process> setupCoroutine;
|
|
||||||
Permissions permissions;
|
Permissions permissions;
|
||||||
|
|
||||||
~Project();
|
~Project();
|
||||||
|
|
||||||
dv::value serialize() const override;
|
dv::value serialize() const override;
|
||||||
void deserialize(const dv::value& src) override;
|
void deserialize(const dv::value& src) override;
|
||||||
|
|
||||||
void loadProjectClientScript();
|
|
||||||
void loadProjectStartScript();
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
#include "content/ContentControl.hpp"
|
#include "content/ContentControl.hpp"
|
||||||
#include "core_defs.hpp"
|
#include "core_defs.hpp"
|
||||||
#include "debug/Logger.hpp"
|
#include "debug/Logger.hpp"
|
||||||
|
#include "devtools/AppScriptsControl.hpp"
|
||||||
#include "devtools/DebuggingServer.hpp"
|
#include "devtools/DebuggingServer.hpp"
|
||||||
#include "devtools/Editor.hpp"
|
#include "devtools/Editor.hpp"
|
||||||
#include "devtools/Project.hpp"
|
#include "devtools/Project.hpp"
|
||||||
|
|
@ -191,10 +192,8 @@ void Engine::initialize(CoreParameters coreParameters) {
|
||||||
audio::set_input_device(name == "auto" ? "" : name);
|
audio::set_input_device(name == "auto" ? "" : name);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
project->loadProjectStartScript();
|
appScripts = std::make_unique<AppScriptsControl>(params, *project);
|
||||||
if (!params.headless) {
|
|
||||||
project->loadProjectClientScript();
|
|
||||||
}
|
|
||||||
if (params.stdinCommands) {
|
if (params.stdinCommands) {
|
||||||
cmd::start_stdin_cmd_reader(*this);
|
cmd::start_stdin_cmd_reader(*this);
|
||||||
}
|
}
|
||||||
|
|
@ -263,9 +262,7 @@ void Engine::detachDebugger() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::applicationTick() {
|
void Engine::applicationTick() {
|
||||||
if (project->setupCoroutine && project->setupCoroutine->isActive()) {
|
appScripts->tick();
|
||||||
project->setupCoroutine->update();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::updateFrontend() {
|
void Engine::updateFrontend() {
|
||||||
|
|
@ -353,6 +350,7 @@ void Engine::close() {
|
||||||
network.reset();
|
network.reset();
|
||||||
clearKeepedObjects();
|
clearKeepedObjects();
|
||||||
project.reset();
|
project.reset();
|
||||||
|
appScripts.reset();
|
||||||
scripting::close();
|
scripting::close();
|
||||||
logger.info() << "scripting finished";
|
logger.info() << "scripting finished";
|
||||||
if (!params.headless) {
|
if (!params.headless) {
|
||||||
|
|
@ -387,8 +385,8 @@ void Engine::loadProject() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::setScreen(std::shared_ptr<Screen> screen) {
|
void Engine::setScreen(std::shared_ptr<Screen> screen) {
|
||||||
if (project->clientScript && this->screen) {
|
if (this->screen) {
|
||||||
project->clientScript->onScreenChange(this->screen->getName(), false);
|
appScripts->onScreenChange(this->screen->getName(), false);
|
||||||
}
|
}
|
||||||
// reset audio channels (stop all sources)
|
// reset audio channels (stop all sources)
|
||||||
audio::reset_channel(audio::get_channel_index("regular"));
|
audio::reset_channel(audio::get_channel_index("regular"));
|
||||||
|
|
@ -397,8 +395,8 @@ void Engine::setScreen(std::shared_ptr<Screen> screen) {
|
||||||
if (this->screen) {
|
if (this->screen) {
|
||||||
this->screen->onOpen();
|
this->screen->onOpen();
|
||||||
}
|
}
|
||||||
if (project->clientScript && this->screen) {
|
if (this->screen) {
|
||||||
project->clientScript->onScreenChange(this->screen->getName(), true);
|
appScripts->onScreenChange(this->screen->getName(), true);
|
||||||
window->setShouldRefresh();
|
window->setShouldRefresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
class Assets;
|
class Assets;
|
||||||
class AssetsLoader;
|
class AssetsLoader;
|
||||||
class AssetsManagement;
|
class AssetsManagement;
|
||||||
|
class AppScriptsControl;
|
||||||
class ContentControl;
|
class ContentControl;
|
||||||
class EngineController;
|
class EngineController;
|
||||||
class EnginePaths;
|
class EnginePaths;
|
||||||
|
|
@ -55,6 +56,7 @@ class Engine : public util::ObjectsKeeper {
|
||||||
std::unique_ptr<Project> project;
|
std::unique_ptr<Project> project;
|
||||||
std::unique_ptr<SettingsHandler> settingsHandler;
|
std::unique_ptr<SettingsHandler> settingsHandler;
|
||||||
std::unique_ptr<AssetsManagement> assets;
|
std::unique_ptr<AssetsManagement> assets;
|
||||||
|
std::unique_ptr<AppScriptsControl> appScripts;
|
||||||
std::shared_ptr<Screen> screen;
|
std::shared_ptr<Screen> screen;
|
||||||
std::unique_ptr<ContentControl> content;
|
std::unique_ptr<ContentControl> content;
|
||||||
std::unique_ptr<EngineController> controller;
|
std::unique_ptr<EngineController> controller;
|
||||||
|
|
@ -184,5 +186,9 @@ public:
|
||||||
return debuggingServer.get();
|
return debuggingServer.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AppScriptsControl& getAppScripts() {
|
||||||
|
return *appScripts;
|
||||||
|
}
|
||||||
|
|
||||||
void detachDebugger();
|
void detachDebugger();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
#include "Mainloop.hpp"
|
#include "Mainloop.hpp"
|
||||||
|
|
||||||
#include "Engine.hpp"
|
|
||||||
#include "debug/Logger.hpp"
|
#include "debug/Logger.hpp"
|
||||||
|
#include "devtools/AppScriptsControl.hpp"
|
||||||
#include "devtools/Project.hpp"
|
#include "devtools/Project.hpp"
|
||||||
#include "frontend/screens/MenuScreen.hpp"
|
#include "Engine.hpp"
|
||||||
#include "frontend/screens/LevelScreen.hpp"
|
#include "frontend/screens/LevelScreen.hpp"
|
||||||
|
#include "frontend/screens/MenuScreen.hpp"
|
||||||
|
#include "graphics/ui/elements/Container.hpp"
|
||||||
|
#include "graphics/ui/GUI.hpp"
|
||||||
|
#include "io/path.hpp"
|
||||||
|
#include "logic/scripting/scripting.hpp"
|
||||||
#include "window/Window.hpp"
|
#include "window/Window.hpp"
|
||||||
#include "world/Level.hpp"
|
#include "world/Level.hpp"
|
||||||
#include "graphics/ui/GUI.hpp"
|
|
||||||
#include "graphics/ui/elements/Container.hpp"
|
|
||||||
|
|
||||||
static debug::Logger logger("mainloop");
|
static debug::Logger logger("mainloop");
|
||||||
|
|
||||||
|
|
@ -19,6 +22,7 @@ void Mainloop::run() {
|
||||||
auto& time = engine.getTime();
|
auto& time = engine.getTime();
|
||||||
auto& window = engine.getWindow();
|
auto& window = engine.getWindow();
|
||||||
auto& settings = engine.getSettings();
|
auto& settings = engine.getSettings();
|
||||||
|
const auto& coreParams = engine.getCoreParameters();
|
||||||
|
|
||||||
engine.setLevelConsumer([this](auto level, int64_t localPlayer) {
|
engine.setLevelConsumer([this](auto level, int64_t localPlayer) {
|
||||||
if (level == nullptr) {
|
if (level == nullptr) {
|
||||||
|
|
@ -35,9 +39,11 @@ void Mainloop::run() {
|
||||||
|
|
||||||
logger.info() << "starting menu screen";
|
logger.info() << "starting menu screen";
|
||||||
engine.setScreen(std::make_shared<MenuScreen>(engine));
|
engine.setScreen(std::make_shared<MenuScreen>(engine));
|
||||||
|
|
||||||
|
auto& appScripts = engine.getAppScripts();
|
||||||
|
|
||||||
logger.info() << "main loop started";
|
logger.info() << "main loop started";
|
||||||
while (!window.isShouldClose()){
|
while (!window.isShouldClose()) {
|
||||||
time.update(window.time());
|
time.update(window.time());
|
||||||
engine.applicationTick();
|
engine.applicationTick();
|
||||||
engine.updateFrontend();
|
engine.updateFrontend();
|
||||||
|
|
@ -50,6 +56,10 @@ void Mainloop::run() {
|
||||||
settings.display.adaptiveFpsInMenu.get() &&
|
settings.display.adaptiveFpsInMenu.get() &&
|
||||||
dynamic_cast<const MenuScreen*>(engine.getScreen().get()) != nullptr
|
dynamic_cast<const MenuScreen*>(engine.getScreen().get()) != nullptr
|
||||||
);
|
);
|
||||||
|
if (coreParams.testMode && appScripts.isFinished()) {
|
||||||
|
logger.info() << "test finished";
|
||||||
|
engine.quit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
logger.info() << "main loop stopped";
|
logger.info() << "main loop stopped";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
#include "ServerMainloop.hpp"
|
#include "ServerMainloop.hpp"
|
||||||
|
|
||||||
#include "Engine.hpp"
|
#include "Engine.hpp"
|
||||||
#include "logic/scripting/scripting.hpp"
|
#include "devtools/AppScriptsControl.hpp"
|
||||||
#include "logic/LevelController.hpp"
|
#include "logic/LevelController.hpp"
|
||||||
#include "interfaces/Process.hpp"
|
#include "interfaces/Process.hpp"
|
||||||
#include "debug/Logger.hpp"
|
#include "debug/Logger.hpp"
|
||||||
#include "world/Level.hpp"
|
#include "world/Level.hpp"
|
||||||
#include "world/World.hpp"
|
#include "world/World.hpp"
|
||||||
#include "util/platform.hpp"
|
#include "util/platform.hpp"
|
||||||
|
#include "devtools/Project.hpp"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
|
|
@ -32,21 +33,12 @@ void ServerMainloop::run() {
|
||||||
setLevel(std::move(level));
|
setLevel(std::move(level));
|
||||||
});
|
});
|
||||||
|
|
||||||
auto process = scripting::start_app_script(
|
|
||||||
"script:" + coreParams.scriptFile.filename().u8string()
|
|
||||||
);
|
|
||||||
|
|
||||||
double targetDelta = 1.0 / static_cast<double>(coreParams.tps);
|
double targetDelta = 1.0 / static_cast<double>(coreParams.tps);
|
||||||
double delta = targetDelta;
|
double delta = targetDelta;
|
||||||
auto begin = system_clock::now();
|
auto begin = system_clock::now();
|
||||||
auto startupTime = begin;
|
auto startupTime = begin;
|
||||||
|
|
||||||
while (process->isActive()) {
|
while (!engine.isQuitSignal() && !engine.getAppScripts().isFinished()) {
|
||||||
if (engine.isQuitSignal()) {
|
|
||||||
process->terminate();
|
|
||||||
logger.info() << "script has been terminated due to quit signal";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (coreParams.testMode) {
|
if (coreParams.testMode) {
|
||||||
time.step(delta);
|
time.step(delta);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -55,7 +47,6 @@ void ServerMainloop::run() {
|
||||||
duration_cast<microseconds>(now - startupTime).count() / 1e6);
|
duration_cast<microseconds>(now - startupTime).count() / 1e6);
|
||||||
delta = time.getDelta();
|
delta = time.getDelta();
|
||||||
}
|
}
|
||||||
process->update();
|
|
||||||
if (controller) {
|
if (controller) {
|
||||||
controller->getLevel()->getWorld()->updateTimers(delta);
|
controller->getLevel()->getWorld()->updateTimers(delta);
|
||||||
controller->update(glm::min(delta, 0.2), false);
|
controller->update(glm::min(delta, 0.2), false);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@
|
||||||
#include "graphics/ui/elements/InventoryView.hpp"
|
#include "graphics/ui/elements/InventoryView.hpp"
|
||||||
#include "graphics/ui/gui_xml.hpp"
|
#include "graphics/ui/gui_xml.hpp"
|
||||||
#include "logic/scripting/scripting.hpp"
|
#include "logic/scripting/scripting.hpp"
|
||||||
|
#include "debug/Logger.hpp"
|
||||||
|
|
||||||
|
static debug::Logger logger("ui-document");
|
||||||
|
|
||||||
UiDocument::UiDocument(
|
UiDocument::UiDocument(
|
||||||
std::string id,
|
std::string id,
|
||||||
|
|
@ -17,6 +20,19 @@ UiDocument::UiDocument(
|
||||||
rebuildIndices();
|
rebuildIndices();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UiDocument::~UiDocument() {
|
||||||
|
try {
|
||||||
|
scripting::on_ui_destroy(*this);
|
||||||
|
} catch (const std::exception& err) {
|
||||||
|
logger.error() << "an error occurred on calling on_destroy event for document '"
|
||||||
|
<< id << "': " << err.what();
|
||||||
|
} catch (...) {
|
||||||
|
logger.error() << "unknown exception caught on calling on_destroy "
|
||||||
|
"event for document '"
|
||||||
|
<< id << "'";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void UiDocument::rebuildIndices() {
|
void UiDocument::rebuildIndices() {
|
||||||
map.clear();
|
map.clear();
|
||||||
gui::UINode::getIndices(root, map);
|
gui::UINode::getIndices(root, map);
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ struct UiDocScript {
|
||||||
bool onopen : 1;
|
bool onopen : 1;
|
||||||
bool onprogress : 1;
|
bool onprogress : 1;
|
||||||
bool onclose : 1;
|
bool onclose : 1;
|
||||||
|
bool ondestroy : 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
using UINodesMap = std::unordered_map<std::string, std::weak_ptr<gui::UINode>>;
|
using UINodesMap = std::unordered_map<std::string, std::weak_ptr<gui::UINode>>;
|
||||||
|
|
@ -35,6 +36,8 @@ public:
|
||||||
scriptenv env
|
scriptenv env
|
||||||
);
|
);
|
||||||
|
|
||||||
|
~UiDocument();
|
||||||
|
|
||||||
void rebuildIndices();
|
void rebuildIndices();
|
||||||
void pushIndices(const std::shared_ptr<gui::UINode>& node);
|
void pushIndices(const std::shared_ptr<gui::UINode>& node);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -563,7 +563,7 @@ void Hud::add(const HudElement& element, const dv::value& argsArray) {
|
||||||
args.emplace_back(static_cast<integer_t>(blockPos[i]));
|
args.emplace_back(static_cast<integer_t>(blockPos[i]));
|
||||||
}
|
}
|
||||||
scripting::on_ui_open(
|
scripting::on_ui_open(
|
||||||
element.getDocument(),
|
*element.getDocument(),
|
||||||
std::move(args)
|
std::move(args)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -578,7 +578,7 @@ void Hud::onRemove(const HudElement& element) {
|
||||||
if (invview) {
|
if (invview) {
|
||||||
inventory = invview->getInventory().get();
|
inventory = invview->getInventory().get();
|
||||||
}
|
}
|
||||||
scripting::on_ui_close(document, inventory);
|
scripting::on_ui_close(*document, inventory);
|
||||||
if (invview) {
|
if (invview) {
|
||||||
invview->unbind();
|
invview->unbind();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ UiDocument* menus::show(
|
||||||
);
|
);
|
||||||
auto document = documentPtr.get();
|
auto document = documentPtr.get();
|
||||||
engine.getAssets()->store(std::move(documentPtr), fullname);
|
engine.getAssets()->store(std::move(documentPtr), fullname);
|
||||||
scripting::on_ui_open(document, std::move(args));
|
scripting::on_ui_open(*document, std::move(args));
|
||||||
menu->addPage(name, document->getRoot());
|
menu->addPage(name, document->getRoot());
|
||||||
menu->setPage(name);
|
menu->setPage(name);
|
||||||
return document;
|
return document;
|
||||||
|
|
@ -99,13 +99,13 @@ void menus::show_process_panel(
|
||||||
|
|
||||||
auto menu = engine.getGUI().getMenu();
|
auto menu = engine.getGUI().getMenu();
|
||||||
menu->reset();
|
menu->reset();
|
||||||
auto doc =
|
auto document =
|
||||||
menus::show(engine, "process", {util::wstr2str_utf8(langs::get(text))});
|
menus::show(engine, "process", {util::wstr2str_utf8(langs::get(text))});
|
||||||
std::dynamic_pointer_cast<Container>(doc->getRoot())
|
std::dynamic_pointer_cast<Container>(document->getRoot())
|
||||||
->listenInterval(0.01f, [=]() {
|
->listenInterval(0.01f, [=]() {
|
||||||
task->update();
|
task->update();
|
||||||
|
|
||||||
uint tasksDone = task->getWorkDone();
|
uint tasksDone = task->getWorkDone();
|
||||||
scripting::on_ui_progress(doc, tasksDone, initialWork);
|
scripting::on_ui_progress(*document, tasksDone, initialWork);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,11 @@ Font::Font(
|
||||||
pages(std::move(pages)),
|
pages(std::move(pages)),
|
||||||
glyphs(std::move(glyphs)),
|
glyphs(std::move(glyphs)),
|
||||||
fontFile(std::move(fontFile)) {
|
fontFile(std::move(fontFile)) {
|
||||||
|
if (this->fontFile.has_value()) {
|
||||||
|
if (auto fontPtr = this->fontFile->lock()) {
|
||||||
|
monospace = fontPtr->isMonospace();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Font::~Font() = default;
|
Font::~Font() = default;
|
||||||
|
|
@ -34,6 +39,10 @@ int Font::getYOffset() const {
|
||||||
return yoffset;
|
return yoffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Font::isMonospace() const {
|
||||||
|
return monospace;
|
||||||
|
}
|
||||||
|
|
||||||
int Font::getLineHeight() const {
|
int Font::getLineHeight() const {
|
||||||
return lineHeight;
|
return lineHeight;
|
||||||
}
|
}
|
||||||
|
|
@ -53,7 +62,7 @@ bool Font::isPrintableChar(uint codepoint) const {
|
||||||
|
|
||||||
int FontMetrics::calcWidth(std::wstring_view text, size_t offset, size_t length) const {
|
int FontMetrics::calcWidth(std::wstring_view text, size_t offset, size_t length) const {
|
||||||
auto font = this->font.has_value() ? this->font->lock() : nullptr;
|
auto font = this->font.has_value() ? this->font->lock() : nullptr;
|
||||||
if (font == nullptr) {
|
if (font == nullptr || font->isMonospace()) {
|
||||||
return std::min(text.length() - offset, length) * _glyphInterval;
|
return std::min(text.length() - offset, length) * _glyphInterval;
|
||||||
}
|
}
|
||||||
int totalWidth = 0;
|
int totalWidth = 0;
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ public:
|
||||||
);
|
);
|
||||||
~Font();
|
~Font();
|
||||||
|
|
||||||
|
bool isMonospace() const;
|
||||||
int getLineHeight() const;
|
int getLineHeight() const;
|
||||||
int getYOffset() const;
|
int getYOffset() const;
|
||||||
|
|
||||||
|
|
@ -94,6 +95,7 @@ private:
|
||||||
int lineHeight;
|
int lineHeight;
|
||||||
int yoffset;
|
int yoffset;
|
||||||
int glyphInterval;
|
int glyphInterval;
|
||||||
|
bool monospace = true;
|
||||||
std::vector<std::unique_ptr<Texture>> pages;
|
std::vector<std::unique_ptr<Texture>> pages;
|
||||||
std::vector<Glyph> glyphs;
|
std::vector<Glyph> glyphs;
|
||||||
std::optional<std::weak_ptr<vector_fonts::FontFile>> fontFile;
|
std::optional<std::weak_ptr<vector_fonts::FontFile>> fontFile;
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ InlineFrame::~InlineFrame() = default;
|
||||||
void InlineFrame::setSrc(const std::string& src) {
|
void InlineFrame::setSrc(const std::string& src) {
|
||||||
this->src = src;
|
this->src = src;
|
||||||
if (document) {
|
if (document) {
|
||||||
scripting::on_ui_close(document.get(), nullptr);
|
scripting::on_ui_close(*document, nullptr);
|
||||||
document = nullptr;
|
document = nullptr;
|
||||||
root = nullptr;
|
root = nullptr;
|
||||||
}
|
}
|
||||||
|
|
@ -31,7 +31,7 @@ void InlineFrame::setDocument(const std::shared_ptr<UiDocument>& document) {
|
||||||
root->setSize(size);
|
root->setSize(size);
|
||||||
|
|
||||||
gui.postRunnable([this]() {
|
gui.postRunnable([this]() {
|
||||||
scripting::on_ui_open(this->document.get(), {});
|
scripting::on_ui_open(*this->document, {});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,33 @@ public:
|
||||||
memory_streambuf(const memory_streambuf&) = delete;
|
memory_streambuf(const memory_streambuf&) = delete;
|
||||||
memory_streambuf& operator=(const memory_streambuf&) = delete;
|
memory_streambuf& operator=(const memory_streambuf&) = delete;
|
||||||
|
|
||||||
|
pos_type seekoff(off_type off, std::ios_base::seekdir way,
|
||||||
|
std::ios_base::openmode mode = std::ios_base::in) override {
|
||||||
|
if (!(mode & std::ios_base::in)) return pos_type(off_type(-1));
|
||||||
|
char* base = eback();
|
||||||
|
char* end = egptr();
|
||||||
|
if (!base || !end) return pos_type(off_type(-1));
|
||||||
|
|
||||||
|
std::ptrdiff_t current = gptr() - base;
|
||||||
|
std::ptrdiff_t newpos;
|
||||||
|
if (way == std::ios_base::beg) newpos = off;
|
||||||
|
else if (way == std::ios_base::cur) newpos = current + off;
|
||||||
|
else if (way == std::ios_base::end) newpos = (end - base) + off;
|
||||||
|
else return pos_type(off_type(-1));
|
||||||
|
|
||||||
|
if (newpos < 0 || base + newpos > end) return pos_type(off_type(-1));
|
||||||
|
setg(base, base + newpos, end);
|
||||||
|
return pos_type(off_type(newpos));
|
||||||
|
}
|
||||||
|
|
||||||
|
pos_type seekpos(pos_type sp, std::ios_base::openmode mode = std::ios_base::in) override {
|
||||||
|
return seekoff(off_type(sp), std::ios_base::beg, mode);
|
||||||
|
}
|
||||||
protected:
|
protected:
|
||||||
int_type underflow() override {
|
int_type underflow() override {
|
||||||
|
if (gptr() < egptr()) {
|
||||||
|
return traits_type::to_int_type(*gptr());
|
||||||
|
}
|
||||||
return traits_type::eof();
|
return traits_type::eof();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,7 +61,7 @@ private:
|
||||||
class memory_view_streambuf : public std::streambuf {
|
class memory_view_streambuf : public std::streambuf {
|
||||||
public:
|
public:
|
||||||
explicit memory_view_streambuf(const util::Buffer<char>& buffer)
|
explicit memory_view_streambuf(const util::Buffer<char>& buffer)
|
||||||
: buffer(std::move(buffer)) {
|
: buffer(buffer) {
|
||||||
char* base = const_cast<char*>(this->buffer.data());
|
char* base = const_cast<char*>(this->buffer.data());
|
||||||
char* end = base + this->buffer.size();
|
char* end = base + this->buffer.size();
|
||||||
setg(base, base, end);
|
setg(base, base, end);
|
||||||
|
|
@ -45,8 +70,33 @@ public:
|
||||||
memory_view_streambuf(const memory_view_streambuf&) = delete;
|
memory_view_streambuf(const memory_view_streambuf&) = delete;
|
||||||
memory_view_streambuf& operator=(const memory_view_streambuf&) = delete;
|
memory_view_streambuf& operator=(const memory_view_streambuf&) = delete;
|
||||||
|
|
||||||
|
pos_type seekoff(off_type off, std::ios_base::seekdir way,
|
||||||
|
std::ios_base::openmode mode = std::ios_base::in) override {
|
||||||
|
if (!(mode & std::ios_base::in)) return pos_type(off_type(-1));
|
||||||
|
char* base = eback();
|
||||||
|
char* end = egptr();
|
||||||
|
if (!base || !end) return pos_type(off_type(-1));
|
||||||
|
|
||||||
|
std::ptrdiff_t current = gptr() - base;
|
||||||
|
std::ptrdiff_t newpos;
|
||||||
|
if (way == std::ios_base::beg) newpos = off;
|
||||||
|
else if (way == std::ios_base::cur) newpos = current + off;
|
||||||
|
else if (way == std::ios_base::end) newpos = (end - base) + off;
|
||||||
|
else return pos_type(off_type(-1));
|
||||||
|
|
||||||
|
if (newpos < 0 || base + newpos > end) return pos_type(off_type(-1));
|
||||||
|
setg(base, base + newpos, end);
|
||||||
|
return pos_type(off_type(newpos));
|
||||||
|
}
|
||||||
|
|
||||||
|
pos_type seekpos(pos_type sp, std::ios_base::openmode mode = std::ios_base::in) override {
|
||||||
|
return seekoff(off_type(sp), std::ios_base::beg, mode);
|
||||||
|
}
|
||||||
protected:
|
protected:
|
||||||
int_type underflow() override {
|
int_type underflow() override {
|
||||||
|
if (gptr() < egptr()) {
|
||||||
|
return traits_type::to_int_type(*gptr());
|
||||||
|
}
|
||||||
return traits_type::eof();
|
return traits_type::eof();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
133
src/logic/scripting/io_descriptors.cpp
Normal file
133
src/logic/scripting/io_descriptors.cpp
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
#include "logic/scripting/io_descriptors.hpp"
|
||||||
|
|
||||||
|
#include "debug/Logger.hpp"
|
||||||
|
#include "io/io.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
static debug::Logger logger("descriptors-manager");
|
||||||
|
|
||||||
|
using namespace scripting;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct StreamDescriptor {
|
||||||
|
// TODO: std::iostream?
|
||||||
|
std::unique_ptr<std::istream> in;
|
||||||
|
std::unique_ptr<std::ostream> out;
|
||||||
|
};
|
||||||
|
std::vector<std::optional<StreamDescriptor>> descriptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::istream* io_descriptors::get_input(int id) {
|
||||||
|
if (!is_readable(id)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return ::descriptors[id]->in.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream* io_descriptors::get_output(int id) {
|
||||||
|
if (!is_writeable(id)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return ::descriptors[id]->out.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
static StreamDescriptor& require_descriptor(int id) {
|
||||||
|
if (!io_descriptors::has_descriptor(id)) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"io-descriptor with id " + std::to_string(id) + " does not exists"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return *::descriptors[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::istream& io_descriptors::require_input(int id) {
|
||||||
|
const auto& descriptor = require_descriptor(id);
|
||||||
|
if (descriptor.in) {
|
||||||
|
return *descriptor.in;
|
||||||
|
}
|
||||||
|
throw std::runtime_error("io-descriptor is not readable");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& io_descriptors::require_output(int id) {
|
||||||
|
const auto& descriptor = require_descriptor(id);
|
||||||
|
if (descriptor.out) {
|
||||||
|
return *descriptor.out;
|
||||||
|
}
|
||||||
|
throw std::runtime_error("io-descriptor is not writeable");
|
||||||
|
}
|
||||||
|
|
||||||
|
void io_descriptors::flush(int id) {
|
||||||
|
if (is_writeable(id)) {
|
||||||
|
::descriptors[id]->out->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool io_descriptors::has_descriptor(int id) {
|
||||||
|
return id >= 0 && id < static_cast<int>(::descriptors.size()) &&
|
||||||
|
::descriptors[id].has_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool io_descriptors::is_readable(int id) {
|
||||||
|
return has_descriptor(id) && ::descriptors[id]->in != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool io_descriptors::is_writeable(int id) {
|
||||||
|
return id >= 0 && id < static_cast<int>(::descriptors.size())
|
||||||
|
&& ::descriptors[id].has_value()
|
||||||
|
&& ::descriptors[id]->out != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void io_descriptors::close(int id) {
|
||||||
|
if (!has_descriptor(id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto& desc = ::descriptors[id].value();
|
||||||
|
if (desc.out) {
|
||||||
|
desc.out->flush();
|
||||||
|
}
|
||||||
|
desc.in.reset();
|
||||||
|
desc.out.reset();
|
||||||
|
::descriptors[id] = std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
int io_descriptors::open_descriptor(const io::path& path, bool write, bool read) {
|
||||||
|
std::unique_ptr<std::istream> in;
|
||||||
|
std::unique_ptr<std::ostream> out;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (read) {
|
||||||
|
in = io::read(path);
|
||||||
|
}
|
||||||
|
if (write) {
|
||||||
|
out = io::write(path);
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
logger.error() << "failed to open descriptor for " << path.string()
|
||||||
|
<< ": " << e.what();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < static_cast<int>(descriptors.size()); ++i) {
|
||||||
|
if (!descriptors[i].has_value()) {
|
||||||
|
descriptors[i] = StreamDescriptor{ std::move(in), std::move(out) };
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::descriptors.emplace_back(StreamDescriptor{ std::move(in), std::move(out) });
|
||||||
|
return static_cast<int>(::descriptors.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void io_descriptors::close_all_descriptors() {
|
||||||
|
for (int i = 0; i < static_cast<int>(::descriptors.size()); ++i) {
|
||||||
|
if (::descriptors[i].has_value()) {
|
||||||
|
close(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
::descriptors.clear();
|
||||||
|
}
|
||||||
26
src/logic/scripting/io_descriptors.hpp
Normal file
26
src/logic/scripting/io_descriptors.hpp
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <istream>
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
|
#include "io/fwd.hpp"
|
||||||
|
|
||||||
|
namespace scripting::io_descriptors {
|
||||||
|
std::istream* get_input(int id);
|
||||||
|
std::ostream* get_output(int id);
|
||||||
|
|
||||||
|
std::istream& require_input(int id);
|
||||||
|
std::ostream& require_output(int id);
|
||||||
|
|
||||||
|
void flush(int id);
|
||||||
|
|
||||||
|
bool has_descriptor(int id);
|
||||||
|
|
||||||
|
bool is_readable(int id);
|
||||||
|
bool is_writeable(int id);
|
||||||
|
|
||||||
|
void close(int id);
|
||||||
|
int open_descriptor(const io::path& path, bool write, bool read);
|
||||||
|
|
||||||
|
void close_all_descriptors();
|
||||||
|
}
|
||||||
|
|
@ -44,6 +44,7 @@ extern const luaL_Reg posteffectslib[]; // gfx.posteffects
|
||||||
extern const luaL_Reg quatlib[];
|
extern const luaL_Reg quatlib[];
|
||||||
extern const luaL_Reg randomlib[];
|
extern const luaL_Reg randomlib[];
|
||||||
extern const luaL_Reg compressionlib[];
|
extern const luaL_Reg compressionlib[];
|
||||||
|
extern const luaL_Reg testlib[];
|
||||||
extern const luaL_Reg text3dlib[]; // gfx.text3d
|
extern const luaL_Reg text3dlib[]; // gfx.text3d
|
||||||
extern const luaL_Reg timelib[];
|
extern const luaL_Reg timelib[];
|
||||||
extern const luaL_Reg tomllib[];
|
extern const luaL_Reg tomllib[];
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
#include "util/stringutil.hpp"
|
#include "util/stringutil.hpp"
|
||||||
#include "api_lua.hpp"
|
#include "api_lua.hpp"
|
||||||
#include "../lua_engine.hpp"
|
#include "../lua_engine.hpp"
|
||||||
#include "logic/scripting/descriptors_manager.hpp"
|
#include "logic/scripting/io_descriptors.hpp"
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
using namespace scripting;
|
using namespace scripting;
|
||||||
|
|
@ -263,13 +263,13 @@ static int l_open_descriptor(lua::State* L) {
|
||||||
std::vector<char> buffer;
|
std::vector<char> buffer;
|
||||||
|
|
||||||
if(wplusMode) {
|
if(wplusMode) {
|
||||||
int temp_descriptor = scripting::descriptors_manager::open_descriptor(path, false, true);
|
int temp_descriptor = io_descriptors::open_descriptor(path, false, true);
|
||||||
|
|
||||||
if (temp_descriptor == -1) {
|
if (temp_descriptor == -1) {
|
||||||
throw std::runtime_error("failed to open descriptor for initial reading");
|
throw std::runtime_error("failed to open descriptor for initial reading");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* in_stream = scripting::descriptors_manager::get_input(temp_descriptor);
|
auto* in_stream = io_descriptors::get_input(temp_descriptor);
|
||||||
|
|
||||||
in_stream->seekg(0, std::ios::end);
|
in_stream->seekg(0, std::ios::end);
|
||||||
std::streamsize size = in_stream->tellg();
|
std::streamsize size = in_stream->tellg();
|
||||||
|
|
@ -278,17 +278,17 @@ static int l_open_descriptor(lua::State* L) {
|
||||||
buffer.resize(size);
|
buffer.resize(size);
|
||||||
in_stream->read(buffer.data(), size);
|
in_stream->read(buffer.data(), size);
|
||||||
|
|
||||||
scripting::descriptors_manager::close(temp_descriptor);
|
io_descriptors::close(temp_descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
int descriptor = scripting::descriptors_manager::open_descriptor(path, write, read);
|
int descriptor = io_descriptors::open_descriptor(path, write, read);
|
||||||
|
|
||||||
if(descriptor == -1) {
|
if(descriptor == -1) {
|
||||||
throw std::runtime_error("failed to open descriptor");
|
throw std::runtime_error("failed to open descriptor");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(wplusMode) {
|
if(wplusMode) {
|
||||||
auto* out_stream = scripting::descriptors_manager::get_output(descriptor);
|
auto* out_stream = io_descriptors::get_output(descriptor);
|
||||||
out_stream->write(buffer.data(), buffer.size());
|
out_stream->write(buffer.data(), buffer.size());
|
||||||
out_stream->flush();
|
out_stream->flush();
|
||||||
}
|
}
|
||||||
|
|
@ -297,51 +297,31 @@ static int l_open_descriptor(lua::State* L) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static int l_has_descriptor(lua::State* L) {
|
static int l_has_descriptor(lua::State* L) {
|
||||||
return lua::pushboolean(L, scripting::descriptors_manager::has_descriptor(lua::tointeger(L, 1)));
|
return lua::pushboolean(L, io_descriptors::has_descriptor(lua::tointeger(L, 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
static int l_read_descriptor(lua::State* L) {
|
static int l_read_descriptor(lua::State* L) {
|
||||||
int descriptor = lua::tointeger(L, 1);
|
int descriptor = lua::tointeger(L, 1);
|
||||||
|
|
||||||
if (!scripting::descriptors_manager::has_descriptor(descriptor)) {
|
|
||||||
throw std::runtime_error("unknown descriptor");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!scripting::descriptors_manager::is_readable(descriptor)) {
|
|
||||||
throw std::runtime_error("descriptor is not readable");
|
|
||||||
}
|
|
||||||
|
|
||||||
int maxlen = lua::tointeger(L, 2);
|
int maxlen = lua::tointeger(L, 2);
|
||||||
|
|
||||||
auto* stream = scripting::descriptors_manager::get_input(descriptor);
|
auto& stream = io_descriptors::require_input(descriptor);
|
||||||
|
if (stream.eof()) {
|
||||||
|
stream.clear();
|
||||||
|
}
|
||||||
|
|
||||||
util::Buffer<char> buffer(maxlen);
|
util::Buffer<char> buffer(maxlen);
|
||||||
|
stream.read(buffer.data(), maxlen);
|
||||||
stream->read(buffer.data(), maxlen);
|
std::streamsize read_len = stream.gcount();
|
||||||
|
|
||||||
std::streamsize read_len = stream->gcount();
|
|
||||||
|
|
||||||
return lua::create_bytearray(L, buffer.data(), read_len);
|
return lua::create_bytearray(L, buffer.data(), read_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int l_write_descriptor(lua::State* L) {
|
static int l_write_descriptor(lua::State* L) {
|
||||||
int descriptor = lua::tointeger(L, 1);
|
int descriptor = lua::tointeger(L, 1);
|
||||||
|
|
||||||
if (!scripting::descriptors_manager::has_descriptor(descriptor)) {
|
|
||||||
throw std::runtime_error("unknown descriptor");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!scripting::descriptors_manager::is_writeable(descriptor)) {
|
|
||||||
throw std::runtime_error("descriptor is not writeable");
|
|
||||||
}
|
|
||||||
|
|
||||||
auto data = lua::bytearray_as_string(L, 2);
|
auto data = lua::bytearray_as_string(L, 2);
|
||||||
|
|
||||||
auto* stream = scripting::descriptors_manager::get_output(descriptor);
|
auto& stream = io_descriptors::require_output(descriptor);
|
||||||
|
stream.write(data.data(), static_cast<std::streamsize>(data.size()));
|
||||||
stream->write(data.data(), static_cast<std::streamsize>(data.size()));
|
if (!stream.good()) {
|
||||||
|
|
||||||
if (!stream->good()) {
|
|
||||||
throw std::runtime_error("failed to write to stream");
|
throw std::runtime_error("failed to write to stream");
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -350,12 +330,13 @@ static int l_write_descriptor(lua::State* L) {
|
||||||
static int l_seek_descriptor(lua::State* L) {
|
static int l_seek_descriptor(lua::State* L) {
|
||||||
int descriptor = lua::tointeger(L, 1);
|
int descriptor = lua::tointeger(L, 1);
|
||||||
|
|
||||||
if (!scripting::descriptors_manager::has_descriptor(descriptor)) {
|
if (!io_descriptors::has_descriptor(descriptor)) {
|
||||||
throw std::runtime_error("unknown descriptor");
|
throw std::runtime_error("unknown descriptor");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string mode = lua::require_string(L, 2);
|
auto mode = lua::require_string(L, 2);
|
||||||
std::ios_base::seekdir dir;
|
std::ios_base::seekdir dir;
|
||||||
|
auto position = lua::tointeger(L, 3);
|
||||||
|
|
||||||
switch (mode[0]) {
|
switch (mode[0]) {
|
||||||
case 'b':
|
case 'b':
|
||||||
|
|
@ -371,29 +352,51 @@ static int l_seek_descriptor(lua::State* L) {
|
||||||
throw std::runtime_error("invalid seek mode");
|
throw std::runtime_error("invalid seek mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* stream = scripting::descriptors_manager::get_output(descriptor);
|
if (io_descriptors::is_writeable(descriptor)) {
|
||||||
|
auto& stream = io_descriptors::require_output(descriptor);
|
||||||
|
stream.seekp(position, dir);
|
||||||
|
if (!stream.good()) {
|
||||||
|
throw std::runtime_error("failed to seek stream");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (io_descriptors::is_readable(descriptor)) {
|
||||||
|
auto& stream = io_descriptors::require_input(descriptor);
|
||||||
|
stream.seekg(position, dir);
|
||||||
|
if (!stream.good()) {
|
||||||
|
throw std::runtime_error("failed to seek stream");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
stream->seekp(lua::tointeger(L, 3), dir);
|
static int l_tell_descriptor(lua::State* L) {
|
||||||
|
int descriptor = lua::tointeger(L, 1);
|
||||||
|
|
||||||
if (!stream->good()) {
|
if (!io_descriptors::has_descriptor(descriptor)) {
|
||||||
throw std::runtime_error("failed to seek stream");
|
throw std::runtime_error("unknown descriptor");
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
if (io_descriptors::is_writeable(descriptor)) {
|
||||||
|
auto& stream = io_descriptors::require_output(descriptor);
|
||||||
|
return lua::pushinteger(L, stream.tellp());
|
||||||
|
} else {
|
||||||
|
auto& stream = io_descriptors::require_input(descriptor);
|
||||||
|
return lua::pushinteger(L, stream.tellg());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int l_flush_descriptor(lua::State* L) {
|
static int l_flush_descriptor(lua::State* L) {
|
||||||
int descriptor = lua::tointeger(L, 1);
|
int descriptor = lua::tointeger(L, 1);
|
||||||
|
|
||||||
if (!scripting::descriptors_manager::has_descriptor(descriptor)) {
|
if (!io_descriptors::has_descriptor(descriptor)) {
|
||||||
throw std::runtime_error("unknown descriptor");
|
throw std::runtime_error("unknown descriptor");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!scripting::descriptors_manager::is_writeable(descriptor)) {
|
if (!io_descriptors::is_writeable(descriptor)) {
|
||||||
throw std::runtime_error("descriptor is not writeable");
|
throw std::runtime_error("descriptor is not writeable");
|
||||||
}
|
}
|
||||||
|
|
||||||
scripting::descriptors_manager::flush(descriptor);
|
io_descriptors::flush(descriptor);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -414,16 +417,16 @@ static int l_available_descriptor(lua::State* L) {
|
||||||
static int l_close_descriptor(lua::State* L) {
|
static int l_close_descriptor(lua::State* L) {
|
||||||
int descriptor = lua::tointeger(L, 1);
|
int descriptor = lua::tointeger(L, 1);
|
||||||
|
|
||||||
if (!scripting::descriptors_manager::has_descriptor(descriptor)) {
|
if (!io_descriptors::has_descriptor(descriptor)) {
|
||||||
throw std::runtime_error("unknown descriptor");
|
throw std::runtime_error("unknown descriptor");
|
||||||
}
|
}
|
||||||
|
|
||||||
scripting::descriptors_manager::close(descriptor);
|
io_descriptors::close(descriptor);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int l_close_all_descriptors(lua::State* L) {
|
static int l_close_all_descriptors(lua::State* L) {
|
||||||
scripting::descriptors_manager::close_all_descriptors();
|
io_descriptors::close_all_descriptors();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -456,6 +459,7 @@ const luaL_Reg filelib[] = {
|
||||||
{"__read_descriptor", lua::wrap<l_read_descriptor>},
|
{"__read_descriptor", lua::wrap<l_read_descriptor>},
|
||||||
{"__write_descriptor", lua::wrap<l_write_descriptor>},
|
{"__write_descriptor", lua::wrap<l_write_descriptor>},
|
||||||
{"__seek_descriptor", lua::wrap<l_seek_descriptor>},
|
{"__seek_descriptor", lua::wrap<l_seek_descriptor>},
|
||||||
|
{"__tell_descriptor", lua::wrap<l_tell_descriptor>},
|
||||||
{"__flush_descriptor", lua::wrap<l_flush_descriptor>},
|
{"__flush_descriptor", lua::wrap<l_flush_descriptor>},
|
||||||
{"__available_descriptor", lua::wrap<l_available_descriptor>},
|
{"__available_descriptor", lua::wrap<l_available_descriptor>},
|
||||||
{"__close_descriptor", lua::wrap<l_close_descriptor>},
|
{"__close_descriptor", lua::wrap<l_close_descriptor>},
|
||||||
|
|
|
||||||
|
|
@ -420,15 +420,19 @@ static int p_get_data(UINode* node, lua::State* L) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const std::string& request_node_id(const DocumentNode& docnode) {
|
static const std::string& request_node_id(UiDocument& document, UINode& node) {
|
||||||
std::string id = docnode.node->getId();
|
const std::string& id = node.getId();
|
||||||
if (id.empty()) {
|
if (id.empty()) {
|
||||||
id = "#" + std::to_string(
|
node.setId( "#" + std::to_string(
|
||||||
reinterpret_cast<std::ptrdiff_t>(docnode.node.get()));
|
reinterpret_cast<std::ptrdiff_t>(&node)));
|
||||||
|
document.pushIndices(node.shared_from_this());
|
||||||
|
return node.getId();
|
||||||
}
|
}
|
||||||
docnode.node->setId(std::move(id));
|
return id;
|
||||||
docnode.document->pushIndices(docnode.node);
|
}
|
||||||
return docnode.node->getId();
|
|
||||||
|
static const std::string& request_node_id(const DocumentNode& docnode) {
|
||||||
|
return request_node_id(*docnode.document, *docnode.node);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Push UI-document node object to stack
|
/// @brief Push UI-document node object to stack
|
||||||
|
|
@ -443,15 +447,14 @@ static int push_document_node(lua::State* L, const std::string& id) {
|
||||||
|
|
||||||
static int p_get_parent(UINode* node, lua::State* L) {
|
static int p_get_parent(UINode* node, lua::State* L) {
|
||||||
auto parent = node->getParent();
|
auto parent = node->getParent();
|
||||||
if (!parent) {
|
if (parent == nullptr) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
auto docname = lua::require_string(L, 1);
|
auto docname = lua::require_string(L, 1);
|
||||||
auto element = lua::require_string(L, 2);
|
auto element = lua::require_string(L, 2);
|
||||||
auto docnode = get_document_node_impl(L, docname, element);
|
auto docnode = get_document_node_impl(L, docname, element);
|
||||||
|
|
||||||
const auto& id = request_node_id(docnode);
|
const auto& id = request_node_id(*docnode.document, *parent);
|
||||||
|
|
||||||
return push_document_node(L, id);
|
return push_document_node(L, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1144,7 +1147,7 @@ static int l_gui_load_document(lua::State* L) {
|
||||||
auto document = documentPtr.get();
|
auto document = documentPtr.get();
|
||||||
engine->requireAssets().store(std::move(documentPtr), alias);
|
engine->requireAssets().store(std::move(documentPtr), alias);
|
||||||
|
|
||||||
scripting::on_ui_open(document, {args});
|
scripting::on_ui_open(*document, {args});
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
45
src/logic/scripting/lua/libs/libtest.cpp
Normal file
45
src/logic/scripting/lua/libs/libtest.cpp
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
#include "api_lua.hpp"
|
||||||
|
#include "engine/Engine.hpp"
|
||||||
|
#include "window/input.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace scripting;
|
||||||
|
|
||||||
|
static int l_set_button_pressed(lua::State* L) {
|
||||||
|
int button = lua::touinteger(L, 1);
|
||||||
|
int x = lua::tointeger(L, 2);
|
||||||
|
int y = lua::tointeger(L, 3);
|
||||||
|
bool pressed = lua::toboolean(L, 4);
|
||||||
|
|
||||||
|
auto& input = engine->getInput();
|
||||||
|
input.simulateCursorPos(x, y);
|
||||||
|
input.simulateClick(button, pressed);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int l_set_key_pressed(lua::State* L) {
|
||||||
|
auto key = lua::require_string(L, 1);
|
||||||
|
bool pressed = lua::toboolean(L, 2);
|
||||||
|
|
||||||
|
auto& input = engine->getInput();
|
||||||
|
input.simulateKey(input_util::keycode_from(key), pressed);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int l_enter_text(lua::State* L) {
|
||||||
|
auto string = lua::require_wstring(L, 1);
|
||||||
|
|
||||||
|
auto& input = engine->getInput();
|
||||||
|
for (auto chr : string) {
|
||||||
|
input.simulateCodepoint(chr);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const luaL_Reg testlib[] = {
|
||||||
|
{"set_button_pressed", lua::wrap<l_set_button_pressed>},
|
||||||
|
{"set_key_pressed", lua::wrap<l_set_key_pressed>},
|
||||||
|
{"enter_text", lua::wrap<l_enter_text>},
|
||||||
|
{nullptr, nullptr}
|
||||||
|
};
|
||||||
|
|
@ -129,6 +129,9 @@ static int l_get_generator(lua::State* L) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static int l_get_chunk_data(lua::State* L) {
|
static int l_get_chunk_data(lua::State* L) {
|
||||||
|
if (level == nullptr) {
|
||||||
|
throw std::runtime_error("world is not open");
|
||||||
|
}
|
||||||
int x = static_cast<int>(lua::tointeger(L, 1));
|
int x = static_cast<int>(lua::tointeger(L, 1));
|
||||||
int z = static_cast<int>(lua::tointeger(L, 2));
|
int z = static_cast<int>(lua::tointeger(L, 2));
|
||||||
const auto& chunk = level->chunks->getChunk(x, z);
|
const auto& chunk = level->chunks->getChunk(x, z);
|
||||||
|
|
@ -175,7 +178,7 @@ static void integrate_chunk_client(Chunk& chunk) {
|
||||||
|
|
||||||
static int l_set_chunk_data(lua::State* L) {
|
static int l_set_chunk_data(lua::State* L) {
|
||||||
if (level == nullptr) {
|
if (level == nullptr) {
|
||||||
throw std::runtime_error("no open world");
|
throw std::runtime_error("world is not open");
|
||||||
}
|
}
|
||||||
|
|
||||||
int x = static_cast<int>(lua::tointeger(L, 1));
|
int x = static_cast<int>(lua::tointeger(L, 1));
|
||||||
|
|
@ -201,7 +204,7 @@ static int l_set_chunk_data(lua::State* L) {
|
||||||
|
|
||||||
static int l_save_chunk_data(lua::State* L) {
|
static int l_save_chunk_data(lua::State* L) {
|
||||||
if (level == nullptr) {
|
if (level == nullptr) {
|
||||||
throw std::runtime_error("no open world");
|
throw std::runtime_error("world is not open");
|
||||||
}
|
}
|
||||||
|
|
||||||
int x = static_cast<int>(lua::tointeger(L, 1));
|
int x = static_cast<int>(lua::tointeger(L, 1));
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ namespace {
|
||||||
debug::Logger logger("lua-state");
|
debug::Logger logger("lua-state");
|
||||||
lua::State* main_thread = nullptr;
|
lua::State* main_thread = nullptr;
|
||||||
bool headless_mode = false;
|
bool headless_mode = false;
|
||||||
|
bool test_mode = false;
|
||||||
const std::unordered_map<std::string, std::string>* project_args;
|
const std::unordered_map<std::string, std::string>* project_args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,12 +70,12 @@ static void create_libs(State* L, StateType stateType) {
|
||||||
openlib(L, "yaml", yamllib);
|
openlib(L, "yaml", yamllib);
|
||||||
|
|
||||||
openlib(L, "__vc_app", applib);
|
openlib(L, "__vc_app", applib);
|
||||||
lua::getglobal(L, "__vc_app");
|
getglobal(L, "__vc_app");
|
||||||
lua::setregistry(L, "app");
|
setregistry(L, "app");
|
||||||
|
|
||||||
if (stateType == StateType::SCRIPT) {
|
if (stateType == StateType::SCRIPT) {
|
||||||
lua::getregistry(L, "app");
|
getregistry(L, "app");
|
||||||
lua::setglobal(L, "app");
|
setglobal(L, "app");
|
||||||
}
|
}
|
||||||
if (stateType == StateType::BASE || stateType == StateType::SCRIPT) {
|
if (stateType == StateType::BASE || stateType == StateType::SCRIPT) {
|
||||||
openlib(L, "assets", assetslib);
|
openlib(L, "assets", assetslib);
|
||||||
|
|
@ -99,6 +100,10 @@ static void create_libs(State* L, StateType stateType) {
|
||||||
openlib(L, "__transform", transformlib);
|
openlib(L, "__transform", transformlib);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (::test_mode) {
|
||||||
|
openlib(L, "test", testlib);
|
||||||
|
}
|
||||||
|
|
||||||
addfunc(L, "print", lua::wrap<l_print>);
|
addfunc(L, "print", lua::wrap<l_print>);
|
||||||
addfunc(L, "crc32", lua::wrap<l_crc32>);
|
addfunc(L, "crc32", lua::wrap<l_crc32>);
|
||||||
}
|
}
|
||||||
|
|
@ -191,6 +196,7 @@ void lua::initialize(const EnginePaths& paths, const CoreParameters& params) {
|
||||||
logger.info() << LUAJIT_VERSION;
|
logger.info() << LUAJIT_VERSION;
|
||||||
|
|
||||||
headless_mode = params.headless;
|
headless_mode = params.headless;
|
||||||
|
test_mode = params.testMode;
|
||||||
project_args = ¶ms.projectArgs;
|
project_args = ¶ms.projectArgs;
|
||||||
main_thread = create_state(
|
main_thread = create_state(
|
||||||
paths, params.headless ? StateType::SCRIPT : StateType::BASE
|
paths, params.headless ? StateType::SCRIPT : StateType::BASE
|
||||||
|
|
|
||||||
|
|
@ -591,36 +591,46 @@ bool scripting::on_item_break_block(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void scripting::on_ui_open(
|
static void call_layout_event(const UiDocument& layout, const std::vector<dv::value>& args, const std::string& eventName) {
|
||||||
UiDocument* layout, std::vector<dv::value> args
|
auto L = lua::get_main_state();
|
||||||
) {
|
lua::pushenv(L, *layout.getEnvironment());
|
||||||
auto argsptr =
|
if (lua::getfield(L, eventName)) {
|
||||||
std::make_shared<std::vector<dv::value>>(std::move(args));
|
for (const auto& arg : args) {
|
||||||
std::string name = layout->getId() + ".open";
|
lua::pushvalue(L, arg);
|
||||||
lua::emit_event(lua::get_main_state(), name, [=](auto L) {
|
|
||||||
for (const auto& value : *argsptr) {
|
|
||||||
lua::pushvalue(L, value);
|
|
||||||
}
|
}
|
||||||
return argsptr->size();
|
lua::call_nothrow(L, args.size(), 0);
|
||||||
});
|
}
|
||||||
|
lua::pop(L);
|
||||||
|
}
|
||||||
|
|
||||||
|
void scripting::on_ui_open(
|
||||||
|
const UiDocument& layout, std::vector<dv::value> args
|
||||||
|
) {
|
||||||
|
if (layout.getScript().onopen) {
|
||||||
|
call_layout_event(layout, args, "on_open");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void scripting::on_ui_progress(
|
void scripting::on_ui_progress(
|
||||||
UiDocument* layout, int workDone, int workTotal
|
const UiDocument& layout, int workDone, int workTotal
|
||||||
) {
|
) {
|
||||||
std::string name = layout->getId() + ".progress";
|
if (layout.getScript().onprogress){
|
||||||
lua::emit_event(lua::get_main_state(), name, [=](auto L) {
|
call_layout_event(layout, {workDone, workTotal}, "on_progress");
|
||||||
lua::pushinteger(L, workDone);
|
}
|
||||||
lua::pushinteger(L, workTotal);
|
|
||||||
return 2;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void scripting::on_ui_close(UiDocument* layout, Inventory* inventory) {
|
void scripting::on_ui_close(const UiDocument& layout, Inventory* inventory) {
|
||||||
std::string name = layout->getId() + ".close";
|
if (layout.getScript().onclose) {
|
||||||
lua::emit_event(lua::get_main_state(), name, [inventory](auto L) {
|
call_layout_event(
|
||||||
return lua::pushinteger(L, inventory ? inventory->getId() : 0);
|
layout, {inventory ? inventory->getId() : 0}, "on_close"
|
||||||
});
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void scripting::on_ui_destroy(const UiDocument& layout) {
|
||||||
|
if (layout.getScript().ondestroy) {
|
||||||
|
call_layout_event(layout, {}, "on_destroy");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void scripting::on_scripts_loading() {
|
void scripting::on_scripts_loading() {
|
||||||
|
|
@ -805,11 +815,14 @@ void scripting::load_layout_script(
|
||||||
) {
|
) {
|
||||||
int env = *senv;
|
int env = *senv;
|
||||||
|
|
||||||
lua::pop(lua::get_main_state(), load_script(env, "layout", file, fileName));
|
auto L = lua::get_main_state();
|
||||||
script.onopen = register_event(env, "on_open", prefix + ".open");
|
lua::pop(L, load_script(env, "layout", file, fileName));
|
||||||
script.onprogress =
|
lua::pushenv(L, env);
|
||||||
register_event(env, "on_progress", prefix + ".progress");
|
script.onopen = lua::hasfield(L, "on_open");
|
||||||
script.onclose = register_event(env, "on_close", prefix + ".close");
|
script.onprogress = lua::hasfield(L, "on_progress");
|
||||||
|
script.onclose = lua::hasfield(L, "on_close");
|
||||||
|
script.ondestroy = lua::hasfield(L, "on_destroy");
|
||||||
|
lua::pop(L);
|
||||||
}
|
}
|
||||||
|
|
||||||
void scripting::close() {
|
void scripting::close() {
|
||||||
|
|
|
||||||
|
|
@ -152,13 +152,16 @@ namespace scripting {
|
||||||
void on_attacked(const Entity& entity, Player* player, entityid_t attacker);
|
void on_attacked(const Entity& entity, Player* player, entityid_t attacker);
|
||||||
void on_entity_used(const Entity& entity, Player* player);
|
void on_entity_used(const Entity& entity, Player* player);
|
||||||
|
|
||||||
/// @brief Called on UI view show
|
/// @brief Called on UI document show
|
||||||
void on_ui_open(UiDocument* layout, std::vector<dv::value> args);
|
void on_ui_open(const UiDocument& layout, std::vector<dv::value> args);
|
||||||
|
|
||||||
void on_ui_progress(UiDocument* layout, int workDone, int totalWork);
|
void on_ui_progress(const UiDocument& layout, int workDone, int totalWork);
|
||||||
|
|
||||||
/// @brief Called on UI view close
|
/// @brief Called on UI document close
|
||||||
void on_ui_close(UiDocument* layout, Inventory* inventory);
|
void on_ui_close(const UiDocument& layout, Inventory* inventory);
|
||||||
|
|
||||||
|
/// @brief Called on UI document destroy
|
||||||
|
void on_ui_destroy(const UiDocument& layout);
|
||||||
|
|
||||||
/// @brief Called on Content loading
|
/// @brief Called on Content loading
|
||||||
void on_scripts_loading();
|
void on_scripts_loading();
|
||||||
|
|
|
||||||
|
|
@ -165,17 +165,127 @@ inline constexpr short MOUSE_KEYS_OFFSET = 1024;
|
||||||
|
|
||||||
static GLFWcursor* standard_cursors[static_cast<int>(CursorShape::LAST) + 1] = {};
|
static GLFWcursor* standard_cursors[static_cast<int>(CursorShape::LAST) + 1] = {};
|
||||||
|
|
||||||
class GLFWInput : public Input {
|
class BaseInput : public Input {
|
||||||
public:
|
public:
|
||||||
|
std::vector<uint> codepoints;
|
||||||
int scroll = 0;
|
int scroll = 0;
|
||||||
|
|
||||||
|
void onKeyCallback(int key, bool pressed) {
|
||||||
|
bool prevPressed = keys[key];
|
||||||
|
keys[key] = pressed;
|
||||||
|
frames[key] = currentFrame;
|
||||||
|
if (pressed && !prevPressed) {
|
||||||
|
const auto& callbacks = keyCallbacks.find(static_cast<Keycode>(key));
|
||||||
|
if (callbacks != keyCallbacks.end()) {
|
||||||
|
callbacks->second.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pressed && key < MOUSE_KEYS_OFFSET) {
|
||||||
|
pressedKeys.push_back(static_cast<Keycode>(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onMouseCallback(int button, bool pressed) {
|
||||||
|
int key = button + MOUSE_KEYS_OFFSET;
|
||||||
|
onKeyCallback(key, pressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isCursorLocked() const override {
|
||||||
|
return cursorLocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCursorPosition(double xpos, double ypos) {
|
||||||
|
if (cursorDrag) {
|
||||||
|
delta.x += xpos - cursor.x;
|
||||||
|
delta.y += ypos - cursor.y;
|
||||||
|
} else {
|
||||||
|
cursorDrag = true;
|
||||||
|
}
|
||||||
|
cursor.x = xpos;
|
||||||
|
cursor.y = ypos;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bindings& getBindings() override {
|
||||||
|
return bindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Bindings& getBindings() const override {
|
||||||
|
return bindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObserverHandler addKeyCallback(Keycode key, KeyCallback callback) override {
|
||||||
|
return keyCallbacks[key].add(std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<Keycode>& getPressedKeys() const override {
|
||||||
|
return pressedKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<uint>& getCodepoints() const override {
|
||||||
|
return codepoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
CursorState getCursor() const override {
|
||||||
|
return {isCursorLocked(), cursor, delta};
|
||||||
|
}
|
||||||
|
|
||||||
|
int getScroll() override {
|
||||||
|
return scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pressed(Keycode key) const override {
|
||||||
|
int keycode = static_cast<int>(key);
|
||||||
|
if (keycode < 0 || keycode >= KEYS_BUFFER_SIZE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return keys[keycode];
|
||||||
|
}
|
||||||
|
bool jpressed(Keycode keycode) const override {
|
||||||
|
return pressed(keycode) &&
|
||||||
|
frames[static_cast<int>(keycode)] == currentFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool clicked(Mousecode code) const override {
|
||||||
|
return pressed(
|
||||||
|
static_cast<Keycode>(MOUSE_KEYS_OFFSET + static_cast<int>(code))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
bool jclicked(Mousecode code) const override {
|
||||||
|
return clicked(code) &&
|
||||||
|
frames[static_cast<int>(code) + MOUSE_KEYS_OFFSET] ==
|
||||||
|
currentFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
void simulateKey(Keycode key, bool pressed) override {
|
||||||
|
onKeyCallback(static_cast<int>(key), pressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void simulateClick(int button, bool pressed) override {
|
||||||
|
onMouseCallback(static_cast<int>(button), pressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void simulateCursorPos(double xpos, double ypos) override {
|
||||||
|
setCursorPosition(xpos, ypos);
|
||||||
|
}
|
||||||
|
|
||||||
|
void simulateCodepoint(uint codepoint) override {
|
||||||
|
codepoints.push_back(codepoint);
|
||||||
|
}
|
||||||
|
protected:
|
||||||
uint currentFrame = 0;
|
uint currentFrame = 0;
|
||||||
uint frames[KEYS_BUFFER_SIZE] {};
|
uint frames[KEYS_BUFFER_SIZE] {};
|
||||||
std::vector<uint> codepoints;
|
|
||||||
std::vector<Keycode> pressedKeys;
|
std::vector<Keycode> pressedKeys;
|
||||||
Bindings bindings;
|
Bindings bindings;
|
||||||
bool keys[KEYS_BUFFER_SIZE] {};
|
bool keys[KEYS_BUFFER_SIZE] {};
|
||||||
std::unordered_map<Keycode, util::HandlersList<>> keyCallbacks;
|
std::unordered_map<Keycode, util::HandlersList<>> keyCallbacks;
|
||||||
|
bool cursorLocked = false;
|
||||||
|
bool cursorDrag = false;
|
||||||
|
glm::vec2 delta {};
|
||||||
|
glm::vec2 cursor {};
|
||||||
|
};
|
||||||
|
|
||||||
|
class GLFWInput : public BaseInput {
|
||||||
|
public:
|
||||||
GLFWInput(GLFWwindow* window)
|
GLFWInput(GLFWwindow* window)
|
||||||
: window(window) {
|
: window(window) {
|
||||||
}
|
}
|
||||||
|
|
@ -225,26 +335,6 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onKeyCallback(int key, bool pressed) {
|
|
||||||
bool prevPressed = keys[key];
|
|
||||||
keys[key] = pressed;
|
|
||||||
frames[key] = currentFrame;
|
|
||||||
if (pressed && !prevPressed) {
|
|
||||||
const auto& callbacks = keyCallbacks.find(static_cast<Keycode>(key));
|
|
||||||
if (callbacks != keyCallbacks.end()) {
|
|
||||||
callbacks->second.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (pressed && key < MOUSE_KEYS_OFFSET) {
|
|
||||||
pressedKeys.push_back(static_cast<Keycode>(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onMouseCallback(int button, bool pressed) {
|
|
||||||
int key = button + MOUSE_KEYS_OFFSET;
|
|
||||||
onKeyCallback(key, pressed);
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* getClipboardText() const override {
|
const char* getClipboardText() const override {
|
||||||
return glfwGetClipboardString(window);
|
return glfwGetClipboardString(window);
|
||||||
}
|
}
|
||||||
|
|
@ -253,41 +343,6 @@ public:
|
||||||
glfwSetClipboardString(window, text);
|
glfwSetClipboardString(window, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
int getScroll() override {
|
|
||||||
return scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool pressed(Keycode key) const override {
|
|
||||||
int keycode = static_cast<int>(key);
|
|
||||||
if (keycode < 0 || keycode >= KEYS_BUFFER_SIZE) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return keys[keycode];
|
|
||||||
}
|
|
||||||
bool jpressed(Keycode keycode) const override {
|
|
||||||
return pressed(keycode) &&
|
|
||||||
frames[static_cast<int>(keycode)] == currentFrame;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool clicked(Mousecode code) const override {
|
|
||||||
return pressed(
|
|
||||||
static_cast<Keycode>(MOUSE_KEYS_OFFSET + static_cast<int>(code))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
bool jclicked(Mousecode code) const override {
|
|
||||||
return clicked(code) &&
|
|
||||||
frames[static_cast<int>(code) + MOUSE_KEYS_OFFSET] ==
|
|
||||||
currentFrame;
|
|
||||||
}
|
|
||||||
|
|
||||||
CursorState getCursor() const override {
|
|
||||||
return {isCursorLocked(), cursor, delta};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isCursorLocked() const override {
|
|
||||||
return cursorLocked;
|
|
||||||
}
|
|
||||||
|
|
||||||
void toggleCursor() override {
|
void toggleCursor() override {
|
||||||
cursorDrag = false;
|
cursorDrag = false;
|
||||||
if (cursorLocked) {
|
if (cursorLocked) {
|
||||||
|
|
@ -298,43 +353,8 @@ public:
|
||||||
}
|
}
|
||||||
cursorLocked = !cursorLocked;
|
cursorLocked = !cursorLocked;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setCursorPosition(double xpos, double ypos) {
|
|
||||||
if (cursorDrag) {
|
|
||||||
delta.x += xpos - cursor.x;
|
|
||||||
delta.y += ypos - cursor.y;
|
|
||||||
} else {
|
|
||||||
cursorDrag = true;
|
|
||||||
}
|
|
||||||
cursor.x = xpos;
|
|
||||||
cursor.y = ypos;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bindings& getBindings() override {
|
|
||||||
return bindings;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Bindings& getBindings() const override {
|
|
||||||
return bindings;
|
|
||||||
}
|
|
||||||
|
|
||||||
ObserverHandler addKeyCallback(Keycode key, KeyCallback callback) override {
|
|
||||||
return keyCallbacks[key].add(std::move(callback));
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<Keycode>& getPressedKeys() const override {
|
|
||||||
return pressedKeys;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<uint>& getCodepoints() const override {
|
|
||||||
return codepoints;
|
|
||||||
}
|
|
||||||
private:
|
private:
|
||||||
GLFWwindow* window;
|
GLFWwindow* window;
|
||||||
bool cursorLocked = false;
|
|
||||||
bool cursorDrag = false;
|
|
||||||
glm::vec2 delta {};
|
|
||||||
glm::vec2 cursor {};
|
|
||||||
};
|
};
|
||||||
static_assert(!std::is_abstract<GLFWInput>());
|
static_assert(!std::is_abstract<GLFWInput>());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -274,6 +274,11 @@ public:
|
||||||
virtual bool clicked(Mousecode mousecode) const = 0;
|
virtual bool clicked(Mousecode mousecode) const = 0;
|
||||||
virtual bool jclicked(Mousecode mousecode) const = 0;
|
virtual bool jclicked(Mousecode mousecode) const = 0;
|
||||||
|
|
||||||
|
virtual void simulateKey(Keycode key, bool pressed) = 0;
|
||||||
|
virtual void simulateClick(int button, bool pressed) = 0;
|
||||||
|
virtual void simulateCursorPos(double xpos, double ypos) = 0;
|
||||||
|
virtual void simulateCodepoint(uint codepoint) = 0;
|
||||||
|
|
||||||
virtual CursorState getCursor() const = 0;
|
virtual CursorState getCursor() const = 0;
|
||||||
|
|
||||||
virtual bool isCursorLocked() const = 0;
|
virtual bool isCursorLocked() const = 0;
|
||||||
|
|
|
||||||
|
|
@ -16,4 +16,15 @@ TEST(io, memory_istream) {
|
||||||
ASSERT_EQ(text, std::string(data));
|
ASSERT_EQ(text, std::string(data));
|
||||||
stream.read(text.data(), 1);
|
stream.read(text.data(), 1);
|
||||||
ASSERT_TRUE(stream.eof());
|
ASSERT_TRUE(stream.eof());
|
||||||
|
|
||||||
|
// seek
|
||||||
|
stream.clear();
|
||||||
|
stream.seekg(0);
|
||||||
|
ASSERT_TRUE(stream.good());
|
||||||
|
stream.read(text.data(), n);
|
||||||
|
ASSERT_EQ(text, std::string(data));
|
||||||
|
stream.seekg(-6, std::ios_base::cur);
|
||||||
|
ASSERT_TRUE(stream.good());
|
||||||
|
stream.read(text.data(), 6);
|
||||||
|
ASSERT_EQ(text.substr(0, 6), "world!");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue