Streaming I/O and support of named pipes (#570)
* added streaming i/o for scripting, and a byteutil.get_size function * added i/o stream class, also added named pipes support on lua side via ffi * added constant file.named_pipes_prefix * added buffered and yield modes for io_stream * added new time function for work with UTC - utc_time, utc_offset, local_time * docs updated * constant pid moved to os.pid * now gmtime_s and localtime_s used only in windows
This commit is contained in:
parent
cd2bc8fbf6
commit
aae642a13e
@ -183,3 +183,26 @@ file.join(директория: str, путь: str) --> str
|
||||
Соединяет путь. Пример: `file.join("world:data", "base/config.toml)` -> `world:data/base/config.toml`.
|
||||
|
||||
Следует использовать данную функцию вместо конкатенации с `/`, так как `префикс:/путь` не является валидным.
|
||||
|
||||
```lua
|
||||
file.open(путь: str, режим: str) --> io_stream
|
||||
```
|
||||
|
||||
Открывает поток для записи/чтения в файл по пути `путь`.
|
||||
|
||||
Аргумент `режим` это список отдельных режимов, в котором каждый обозначается одним символом
|
||||
|
||||
`r` - Чтение из файла
|
||||
`w` - Запись в файл
|
||||
`b` - Открыть поток в двоичном режиме (см. `../io_stream.md`)
|
||||
`+` - Работает совместно с `w`. Добавляет к существующим данным новые (`append-mode`)
|
||||
|
||||
```lua
|
||||
file.open_named_pipe(имя: str, режим: str) -> io_stream
|
||||
```
|
||||
|
||||
Открывает поток для записи/чтения в Named Pipe по пути `путь`
|
||||
|
||||
`/tmp/` или `\\\\.\\pipe\\` добавлять не нужно - движок делает это автоматически.
|
||||
|
||||
Доступные режимы такие же, как и в `file.open`, за исключением `+`
|
||||
@ -11,3 +11,21 @@ time.delta() -> float
|
||||
```
|
||||
|
||||
Возвращает дельту времени (время прошедшее с предыдущего кадра)
|
||||
|
||||
```python
|
||||
time.utc_time() -> int
|
||||
```
|
||||
|
||||
Возвращает время UTC в секундах
|
||||
|
||||
```python
|
||||
time.local_time() -> int
|
||||
```
|
||||
|
||||
Возвращает локальное (системное) время в секундах
|
||||
|
||||
```python
|
||||
time.utc_offset() -> int
|
||||
```
|
||||
|
||||
Возвращает смещение локального времени от UTC в секундах
|
||||
@ -258,3 +258,9 @@ function sleep(timesec: number)
|
||||
```
|
||||
|
||||
Вызывает остановку корутины до тех пор, пока не пройдёт количество секунд, указанное в **timesec**. Функция может быть использована только внутри корутины.
|
||||
|
||||
```lua
|
||||
os.pid -> number
|
||||
```
|
||||
|
||||
Константа, в которой хранится PID текущего инстанса движка
|
||||
188
doc/ru/scripting/io_stream.md
Normal file
188
doc/ru/scripting/io_stream.md
Normal file
@ -0,0 +1,188 @@
|
||||
# Класс *io_stream*
|
||||
|
||||
Класс, предназначенный для работы с потоками
|
||||
|
||||
## Режимы
|
||||
|
||||
Поток имеет три различных вида режима:
|
||||
|
||||
**general** - Общий режим работы I/O
|
||||
**binary** - Формат записи и чтения I/O
|
||||
**flush** - Режим работы flush
|
||||
|
||||
### general
|
||||
|
||||
Имеет три режима:
|
||||
|
||||
**default** - Дефолтный режим работы потока. При read может вернуть только часть от требуемых данных, при write сразу записывает данные в поток.
|
||||
|
||||
**yield** - Почти тоже самое, что и **default**, но всегда будет возвращать все требуемые данные. Пока они не будут прочитаны, будет вызывать `coroutine.yield()`. Предназначен для работы в корутинах.
|
||||
|
||||
**buffered** - Буферизирует записываемые и читаемые данные.
|
||||
|
||||
При вызове `available`/`read` обновляет буфер чтения.
|
||||
|
||||
После обновления в `read`, если буфер чтения переполнен, то бросает ошибку `buffer overflow`.
|
||||
|
||||
Если требуемого кол-ва байт недостаточно в буфере для чтения, то бросает ошибку `buffer-underflow`.
|
||||
|
||||
При вызове `write` записывает итоговые байты в буфер для записи. Если он переполнен, то бросает ошибку `buffer overflow`.
|
||||
|
||||
При вызове `flush` проталкивает данные из буфера для записи в напрямую в поток
|
||||
|
||||
### flush
|
||||
|
||||
**all** - Сначала проталкивает данные из буфера напрямую в поток (если используется **buffered** режим), а после вызывает `flush` напрямую из библиотеки
|
||||
|
||||
**buffer** - Только проталкивает данные из буфера в поток (если используется **buffered** режим)
|
||||
|
||||
## Методы
|
||||
|
||||
Методы, позволяющие изменить или получить различные режимы поведения потока
|
||||
|
||||
```lua
|
||||
-- Возвращает true, если поток используется в двоичном режиме
|
||||
io_stream:is_binary_mode() --> bool
|
||||
|
||||
-- Включает или выключает двоичный режим
|
||||
io_stream:set_binary_mode(bool)
|
||||
|
||||
-- Возвращает режим работы потока
|
||||
io_stream:get_mode() --> string
|
||||
|
||||
-- Задаёт режим работы потока. Выбрасывает ошибку, если передан неизвестный режим
|
||||
io_stream:set_mode(string)
|
||||
|
||||
-- Возвращает режим работы flush
|
||||
io_stream:get_flush_mode() --> string
|
||||
|
||||
-- Задаёт режим работы flush
|
||||
io_stream:set_flush_mode(string)
|
||||
```
|
||||
|
||||
I/O методы
|
||||
|
||||
```lua
|
||||
|
||||
--[[
|
||||
Читает данные из потока
|
||||
|
||||
В двоичном режиме:
|
||||
|
||||
Если arg - int, то читает из потока arg байт и возвращает ввиде Bytearray или таблицы, если useTable = true
|
||||
|
||||
Если arg - string, то функция интерпретирует arg как шаблон для byteutil. Прочитает кол-во байт, которое определено шаблоном, передаст их в byteutil.unpack и вернёт результат
|
||||
|
||||
|
||||
В текстовом режиме:
|
||||
|
||||
Если arg - int, то читает нужное кол-во строк с окончанием CRLF/LF из arg и возвращает ввиде таблицы. Также, если trimEmptyLines = true, то удаляет пустые строки с начала и конца из итоговой таблицы
|
||||
|
||||
Если arg не определён, то читает одну строку с окончанием CRLF/LF и возвращает её.
|
||||
--]]
|
||||
io_stream:read(
|
||||
[опционально] arg: int | string,
|
||||
[опционально] useTable | trimEmptyLines: bool
|
||||
) --> Bytearray | table<int> | string | table<string> | ...
|
||||
|
||||
--[[
|
||||
Записывает данные в поток
|
||||
|
||||
В двоичном режиме:
|
||||
|
||||
Если arg - string, то функция интерпретирует arg как шаблон для byteutil, передаст его и ... в byteutil.pack и результат запишет в поток
|
||||
|
||||
Если arg - Bytearray | table<int>, то записывает байты в поток
|
||||
|
||||
В текстовом режиме:
|
||||
|
||||
Если arg - string, то записывает строку в поток (вместе с окончанием LF)
|
||||
|
||||
Если arg - table<string>, то записывает каждую строку из таблицы отдельно
|
||||
--]]
|
||||
io_stream:write(
|
||||
arg: Bytearray | table<int> | string | table<string>,
|
||||
[опционально] ...
|
||||
)
|
||||
|
||||
-- Читает одну строку с окончанием CRLF/LF из потока вне зависимости от двоичного режима
|
||||
io_stream:read_line() --> string
|
||||
|
||||
-- Записывает одну строку с окончанием LF в поток вне зависимости от двоичного режима
|
||||
io_stream:write_line(string)
|
||||
|
||||
--[[
|
||||
|
||||
В двоичном режиме:
|
||||
|
||||
Читает все доступные байты из потока и возвращает ввиде Bytearray или table<int>, если useTable = true
|
||||
|
||||
В текстовом режиме:
|
||||
|
||||
Читает все доступные строки из потока в table<string> если useTable = true, или в одну строку вместе с окончаниями, если нет
|
||||
|
||||
--]]
|
||||
io_stream:read_fully(
|
||||
[опционально] useTable: bool
|
||||
) --> Bytearray | table<int> | table<string> | string
|
||||
```
|
||||
|
||||
Методы, имеющие смысл в использовании только в buffered режиме
|
||||
|
||||
```lua
|
||||
--[[
|
||||
|
||||
Если length определён, то возвращает true, если length байт доступно к чтению. Иначе возвращает false
|
||||
|
||||
Если не определён, то возвращает количество байт, которое можно прочитать
|
||||
|
||||
--]]
|
||||
io_stream:available(
|
||||
[опционально] length: int
|
||||
) --> int | bool
|
||||
|
||||
-- Возвращает максимальный размер буферов
|
||||
io_stream:get_max_buffer_size() --> int
|
||||
|
||||
-- Задаёт новый максимальный размер буферов
|
||||
io_stream:set_max_buffer_size(max_size: int)
|
||||
```
|
||||
|
||||
Методы, контролирующие состояние потока
|
||||
|
||||
```lua
|
||||
|
||||
-- Возвращает true, если поток открыт на данный момент
|
||||
io_stream:is_alive() --> bool
|
||||
|
||||
-- Возвращает true, если поток закрыт на данный момент
|
||||
io_stream:is_closed() --> bool
|
||||
|
||||
-- Закрывает поток
|
||||
io_stream:close()
|
||||
|
||||
--[[
|
||||
|
||||
Записывает все данные из write-буфера в поток в buffer/all flush-режимах
|
||||
Вызывает ioLib.flush() в all flush-режиме
|
||||
|
||||
--]]
|
||||
io_stream:flush()
|
||||
```
|
||||
|
||||
Создание нового потока
|
||||
|
||||
```lua
|
||||
--[[
|
||||
|
||||
Создаёт новый поток с переданным дескриптором и использующим переданную I/O библиотеку. (Более подробно в core:io_stream.lua)
|
||||
|
||||
--]]
|
||||
io_stream.new(
|
||||
descriptor: int,
|
||||
binaryMode: bool,
|
||||
ioLib: table,
|
||||
[опционально] mode: string = "default",
|
||||
[опционально] flushMode: string = "all"
|
||||
) -> io_stream
|
||||
```
|
||||
17
res/modules/internal/stream_providers/file.lua
Normal file
17
res/modules/internal/stream_providers/file.lua
Normal file
@ -0,0 +1,17 @@
|
||||
local io_stream = require "core:io_stream"
|
||||
|
||||
local lib = {
|
||||
read = file.__read_descriptor,
|
||||
write = file.__write_descriptor,
|
||||
flush = file.__flush_descriptor,
|
||||
is_alive = file.__has_descriptor,
|
||||
close = file.__close_descriptor
|
||||
}
|
||||
|
||||
return function(path, mode)
|
||||
return io_stream.new(
|
||||
file.__open_descriptor(path, mode),
|
||||
mode:find('b') ~= nil,
|
||||
lib
|
||||
)
|
||||
end
|
||||
7
res/modules/internal/stream_providers/named_pipe.lua
Normal file
7
res/modules/internal/stream_providers/named_pipe.lua
Normal file
@ -0,0 +1,7 @@
|
||||
local FFI = ffi
|
||||
|
||||
if FFI.os == "Windows" then
|
||||
return require "core:internal/stream_providers/named_pipe_windows"
|
||||
else
|
||||
return require "core:internal/stream_providers/named_pipe_unix"
|
||||
end
|
||||
@ -0,0 +1,21 @@
|
||||
local forbiddenPaths = {
|
||||
"/..\\", "\\../",
|
||||
"/../", "\\..\\"
|
||||
}
|
||||
|
||||
return function(path)
|
||||
local corrected = true
|
||||
|
||||
if path:starts_with("../") or path:starts_with("..\\") then
|
||||
corrected = false
|
||||
else
|
||||
for _, forbiddenPath in ipairs(forbiddenPaths) do
|
||||
if path:find(forbiddenPath) then
|
||||
corrected = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not corrected then error "special path \"../\" is not allowed in path to named pipe" end
|
||||
end
|
||||
104
res/modules/internal/stream_providers/named_pipe_unix.lua
Normal file
104
res/modules/internal/stream_providers/named_pipe_unix.lua
Normal file
@ -0,0 +1,104 @@
|
||||
local path_validate = require "core:internal/stream_providers/named_pipe_path_validate"
|
||||
local io_stream = require "core:io_stream"
|
||||
|
||||
local FFI = ffi
|
||||
|
||||
FFI.cdef[[
|
||||
int open(const char *pathname, int flags);
|
||||
int close(int fd);
|
||||
ssize_t read(int fd, void *buf, size_t count);
|
||||
ssize_t write(int fd, const void *buf, size_t count);
|
||||
int fcntl(int fd, int cmd, ...);
|
||||
|
||||
const char *strerror(int errnum);
|
||||
]]
|
||||
|
||||
local C = FFI.C
|
||||
|
||||
local O_RDONLY = 0x0
|
||||
local O_WRONLY = 0x1
|
||||
local O_RDWR = 0x2
|
||||
local O_NONBLOCK = 0x800
|
||||
local F_GETFL = 3
|
||||
|
||||
local function getError()
|
||||
local err = ffi.errno()
|
||||
|
||||
return ffi.string(C.strerror(err)).." ("..err..")"
|
||||
end
|
||||
|
||||
local lib = {}
|
||||
|
||||
function lib.read(fd, len)
|
||||
local buffer = FFI.new("uint8_t[?]", len)
|
||||
local result = C.read(fd, buffer, len)
|
||||
|
||||
local out = Bytearray()
|
||||
|
||||
if result <= 0 then
|
||||
return out
|
||||
end
|
||||
|
||||
for i = 0, result - 1 do
|
||||
out[i+1] = buffer[i]
|
||||
end
|
||||
|
||||
return out
|
||||
end
|
||||
|
||||
function lib.write(fd, bytearray)
|
||||
local len = #bytearray
|
||||
local buffer = FFI.new("uint8_t[?]", len)
|
||||
for i = 1, len do
|
||||
buffer[i-1] = bytearray[i]
|
||||
end
|
||||
|
||||
if C.write(fd, buffer, len) == -1 then
|
||||
error("failed to write to named pipe: "..getError())
|
||||
end
|
||||
end
|
||||
|
||||
function lib.flush(fd)
|
||||
-- no flush on unix
|
||||
end
|
||||
|
||||
function lib.is_alive(fd)
|
||||
if fd == nil or fd < 0 then return false end
|
||||
|
||||
return C.fcntl(fd, F_GETFL) ~= -1
|
||||
end
|
||||
|
||||
function lib.close(fd)
|
||||
C.close(fd)
|
||||
end
|
||||
|
||||
return function(path, mode)
|
||||
path_validate(path)
|
||||
|
||||
path = "/tmp/"..path
|
||||
|
||||
local read = mode:find('r') ~= nil
|
||||
local write = mode:find('w') ~= nil
|
||||
|
||||
local flags
|
||||
|
||||
if read and write then
|
||||
flags = O_RDWR
|
||||
elseif read then
|
||||
flags = O_RDONLY
|
||||
elseif write then
|
||||
flags = O_WRONLY
|
||||
else
|
||||
error "mode must contain read or write flag"
|
||||
end
|
||||
|
||||
flags = bit.bor(flags, O_NONBLOCK)
|
||||
|
||||
local fd = C.open(path, flags)
|
||||
|
||||
if fd == -1 then
|
||||
error("failed to open named pipe: "..getError())
|
||||
end
|
||||
|
||||
return io_stream.new(fd, mode:find('b') ~= nil, lib)
|
||||
end
|
||||
144
res/modules/internal/stream_providers/named_pipe_windows.lua
Normal file
144
res/modules/internal/stream_providers/named_pipe_windows.lua
Normal file
@ -0,0 +1,144 @@
|
||||
local path_validate = require "core:internal/stream_providers/named_pipe_path_validate"
|
||||
local io_stream = require "core:io_stream"
|
||||
|
||||
local FFI = ffi
|
||||
|
||||
FFI.cdef[[
|
||||
typedef void* HANDLE;
|
||||
typedef uint32_t DWORD;
|
||||
typedef int BOOL;
|
||||
typedef void* LPVOID;
|
||||
typedef const char* LPCSTR;
|
||||
|
||||
BOOL CloseHandle(HANDLE hObject);
|
||||
DWORD GetFileType(HANDLE hFile);
|
||||
BOOL ReadFile(HANDLE hFile, void* lpBuffer, DWORD nNumberOfBytesToRead,
|
||||
DWORD* lpNumberOfBytesRead, void* lpOverlapped);
|
||||
BOOL WriteFile(HANDLE hFile, const void* lpBuffer, DWORD nNumberOfBytesToWrite,
|
||||
DWORD* lpNumberOfBytesWritten, void* lpOverlapped);
|
||||
HANDLE CreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
|
||||
void* lpSecurityAttributes, DWORD dwCreationDisposition,
|
||||
DWORD dwFlagsAndAttributes, HANDLE hTemplateFile);
|
||||
|
||||
BOOL PeekNamedPipe(
|
||||
HANDLE hNamedPipe,
|
||||
LPVOID lpBuffer,
|
||||
DWORD nBufferSize,
|
||||
DWORD* lpBytesRead,
|
||||
DWORD* lpTotalBytesAvail,
|
||||
DWORD* lpBytesLeftThisMessage
|
||||
);
|
||||
|
||||
DWORD GetLastError(void);
|
||||
BOOL FlushFileBuffers(HANDLE hFile);
|
||||
]]
|
||||
|
||||
local C = FFI.C
|
||||
|
||||
local GENERIC_READ = 0x80000000
|
||||
local GENERIC_WRITE = 0x40000000
|
||||
local OPEN_EXISTING = 3
|
||||
local FILE_ATTRIBUTE_NORMAL = 0x00000080
|
||||
local FILE_TYPE_UNKNOWN = 0x0000
|
||||
local INVALID_HANDLE_VALUE = FFI.cast("HANDLE", -1)
|
||||
|
||||
local lib = {}
|
||||
|
||||
local function is_data_available(handle)
|
||||
local bytes_available = FFI.new("DWORD[1]")
|
||||
local success = FFI.C.PeekNamedPipe(handle, nil, 0, nil, bytes_available, nil)
|
||||
|
||||
if success == 0 then
|
||||
return -1
|
||||
end
|
||||
|
||||
return bytes_available[0] > 0
|
||||
end
|
||||
|
||||
function lib.read(handle, len)
|
||||
local out = Bytearray()
|
||||
|
||||
local has_data, err = is_data_available(handle)
|
||||
|
||||
if not has_data then
|
||||
return out
|
||||
elseif hasData == -1 then
|
||||
error("failed to read from named pipe: "..tostring(C.GetLastError()))
|
||||
end
|
||||
|
||||
local buffer = FFI.new("uint8_t[?]", len)
|
||||
local read = FFI.new("DWORD[1]")
|
||||
|
||||
local ok = C.ReadFile(handle, buffer, len, read, nil)
|
||||
|
||||
if ok == 0 or read[0] == 0 then
|
||||
return out
|
||||
end
|
||||
|
||||
for i = 0, read[0] - 1 do
|
||||
out[i+1] = buffer[i]
|
||||
end
|
||||
|
||||
return out
|
||||
end
|
||||
|
||||
function lib.write(handle, bytearray)
|
||||
local len = #bytearray
|
||||
|
||||
local buffer = FFI.new("uint8_t[?]", len)
|
||||
for i = 1, len do
|
||||
buffer[i-1] = bytearray[i]
|
||||
end
|
||||
|
||||
local written = FFI.new("DWORD[1]")
|
||||
|
||||
if C.WriteFile(handle, buffer, len, written, nil) == 0 then
|
||||
error("failed to write to named pipe: "..tostring(C.GetLastError()))
|
||||
end
|
||||
end
|
||||
|
||||
function lib.flush(handle)
|
||||
C.FlushFileBuffers(handle)
|
||||
end
|
||||
|
||||
function lib.is_alive(handle)
|
||||
if handle == nil or handle == INVALID_HANDLE_VALUE then
|
||||
return false
|
||||
else
|
||||
return C.GetFileType(handle) ~= FILE_TYPE_UNKNOWN
|
||||
end
|
||||
end
|
||||
|
||||
function lib.close(handle)
|
||||
C.CloseHandle(handle)
|
||||
end
|
||||
|
||||
return function(path, mode)
|
||||
path_validate(path)
|
||||
|
||||
path = "\\\\.\\pipe\\"..path
|
||||
|
||||
local read = mode:find('r') ~= nil
|
||||
local write = mode:find('w') ~= nil
|
||||
|
||||
local flags
|
||||
|
||||
if read and write then
|
||||
flags = bit.bor(GENERIC_READ, GENERIC_WRITE)
|
||||
elseif read then
|
||||
flags = GENERIC_READ
|
||||
elseif write then
|
||||
flags = GENERIC_WRITE
|
||||
else
|
||||
error("mode must contain read or write flag")
|
||||
end
|
||||
|
||||
local handle = C.CreateFileA(path, flags, 0, nil, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL, nil)
|
||||
|
||||
if handle == INVALID_HANDLE_VALUE then
|
||||
error("failed to open named pipe: "..tostring(C.GetLastError()))
|
||||
end
|
||||
|
||||
return io_stream.new(handle, mode:find('b') ~= nil, lib)
|
||||
end
|
||||
398
res/modules/io_stream.lua
Normal file
398
res/modules/io_stream.lua
Normal file
@ -0,0 +1,398 @@
|
||||
local io_stream = { }
|
||||
|
||||
io_stream.__index = io_stream
|
||||
|
||||
local MAX_BUFFER_SIZE = 8192
|
||||
|
||||
local DEFAULT_MODE = "default"
|
||||
local BUFFERED_MODE = "buffered"
|
||||
local YIELD_MODE = "yield"
|
||||
|
||||
local ALL_MODES = {
|
||||
DEFAULT_MODE,
|
||||
BUFFERED_MODE,
|
||||
YIELD_MODE
|
||||
}
|
||||
|
||||
local FLUSH_MODE_ALL = "all"
|
||||
local FLUSH_MODE_ONLY_BUFFER = "buffer"
|
||||
|
||||
local ALL_FLUSH_MODES = {
|
||||
FLUSH_MODE_ALL,
|
||||
FLUSH_MODE_ONLY_BUFFER
|
||||
}
|
||||
|
||||
local CR = string.byte('\r')
|
||||
local LF = string.byte('\n')
|
||||
|
||||
local function readFully(result, readFunc)
|
||||
local isTable = type(result) == "table"
|
||||
|
||||
local buf
|
||||
|
||||
repeat
|
||||
buf = readFunc(MAX_BUFFER_SIZE)
|
||||
|
||||
if isTable then
|
||||
for i = 1, #buf do
|
||||
result[#result + 1] = buf[i]
|
||||
end
|
||||
else result:append(buf) end
|
||||
until #buf == 0
|
||||
end
|
||||
|
||||
--[[
|
||||
|
||||
descriptor - descriptor of stream for provided I/O library
|
||||
binaryMode - if enabled, most methods will expect bytes instead of strings
|
||||
ioLib - I/O library. Should include the following functions:
|
||||
read(descriptor: int, length: int) -> Bytearray
|
||||
May return bytearray with a smaller size if bytes have not arrived yet or have run out
|
||||
write(descriptor: int, data: Bytearray)
|
||||
flush(descriptor: int)
|
||||
is_alive(descriptor: int) -> bool
|
||||
close(descriptor: int)
|
||||
--]]
|
||||
|
||||
function io_stream.new(descriptor, binaryMode, ioLib, mode, flushMode)
|
||||
mode = mode or DEFAULT_MODE
|
||||
flushMode = flushMode or FLUSH_MODE_ALL
|
||||
|
||||
local self = setmetatable({}, io_stream)
|
||||
|
||||
self.descriptor = descriptor
|
||||
self.binaryMode = binaryMode
|
||||
self.maxBufferSize = MAX_BUFFER_SIZE
|
||||
self.ioLib = ioLib
|
||||
|
||||
self:set_mode(mode)
|
||||
self:set_flush_mode(flushMode)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function io_stream:is_binary_mode()
|
||||
return self.binaryMode
|
||||
end
|
||||
|
||||
function io_stream:set_binary_mode(binaryMode)
|
||||
self.binaryMode = binaryMode ~= nil
|
||||
end
|
||||
|
||||
function io_stream:get_mode()
|
||||
return self.mode
|
||||
end
|
||||
|
||||
function io_stream:set_mode(mode)
|
||||
if not table.has(ALL_MODES, mode) then
|
||||
error("invalid stream mode: "..mode)
|
||||
end
|
||||
|
||||
if self.mode == BUFFERED_MODE then
|
||||
self.writeBuffer:clear()
|
||||
self.readBuffer:clear()
|
||||
end
|
||||
|
||||
if mode == BUFFERED_MODE and not self.writeBuffer then
|
||||
self.writeBuffer = Bytearray()
|
||||
self.readBuffer = Bytearray()
|
||||
end
|
||||
|
||||
self.mode = mode
|
||||
end
|
||||
|
||||
function io_stream:get_flush_mode()
|
||||
return self.flushMode
|
||||
end
|
||||
|
||||
function io_stream:set_flush_mode(flushMode)
|
||||
if not table.has(ALL_FLUSH_MODES, flushMode) then
|
||||
error("invalid flush mode: "..flushMode)
|
||||
end
|
||||
|
||||
self.flushMode = flushMode
|
||||
end
|
||||
|
||||
function io_stream:get_max_buffer_size()
|
||||
return self.maxBufferSize
|
||||
end
|
||||
|
||||
function io_stream:set_max_buffer_size(maxBufferSize)
|
||||
self.maxBufferSize = maxBufferSize
|
||||
end
|
||||
|
||||
function io_stream:available(length)
|
||||
if self.mode == BUFFERED_MODE then
|
||||
self:__update_read_buffer()
|
||||
|
||||
if not length then
|
||||
return #self.readBuffer
|
||||
else
|
||||
return #self.readBuffer >= length
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function io_stream:__update_read_buffer()
|
||||
local readed = Bytearray()
|
||||
|
||||
readFully(readed, function(length) return self.ioLib.read(self.descriptor, length) end)
|
||||
|
||||
self.readBuffer:append(readed)
|
||||
|
||||
if #self.readBuffer > self.maxBufferSize then
|
||||
error "buffer overflow"
|
||||
end
|
||||
end
|
||||
|
||||
function io_stream:__read(length)
|
||||
if self.mode == YIELD_MODE then
|
||||
local buffer = Bytearray()
|
||||
|
||||
while #buffer < length do
|
||||
buffer:append(self.ioLib.read(self.descriptor, length - #buffer))
|
||||
|
||||
if #buffer < length then coroutine.yield() end
|
||||
end
|
||||
|
||||
return buffer
|
||||
elseif self.mode == BUFFERED_MODE then
|
||||
self:__update_read_buffer()
|
||||
|
||||
if #self.readBuffer < length then
|
||||
error "buffer underflow"
|
||||
end
|
||||
|
||||
local copy
|
||||
|
||||
if #self.readBuffer == length then
|
||||
copy = Bytearray()
|
||||
|
||||
copy:append(self.readBuffer)
|
||||
|
||||
self.readBuffer:clear()
|
||||
else
|
||||
copy = Bytearray()
|
||||
|
||||
for i = 1, length do
|
||||
copy[i] = self.readBuffer[i]
|
||||
end
|
||||
|
||||
self.readBuffer:remove(1, length)
|
||||
end
|
||||
|
||||
return copy
|
||||
elseif self.mode == DEFAULT_MODE then
|
||||
return self.ioLib.read(self.descriptor, length)
|
||||
end
|
||||
end
|
||||
|
||||
function io_stream:__write(data)
|
||||
if self.mode == BUFFERED_MODE then
|
||||
self.writeBuffer:append(data)
|
||||
|
||||
if #self.writeBuffer > self.maxBufferSize then
|
||||
error "buffer overflow"
|
||||
end
|
||||
elseif self.mode == DEFAULT_MODE or self.mode == YIELD_MODE then
|
||||
return self.ioLib.write(self.descriptor, data)
|
||||
end
|
||||
end
|
||||
|
||||
function io_stream:read_fully(useTable)
|
||||
if self.binaryMode then
|
||||
local result = useTable and Bytearray() or { }
|
||||
|
||||
readFully(result, function() return self:__read(self.maxBufferSize) end)
|
||||
else
|
||||
if useTable then
|
||||
local lines = { }
|
||||
|
||||
local line
|
||||
|
||||
repeat
|
||||
line = self:read_line()
|
||||
|
||||
lines[#lines + 1] = line
|
||||
until not line
|
||||
|
||||
return lines
|
||||
else
|
||||
local result = Bytearray()
|
||||
|
||||
readFully(result, function() return self:__read(self.maxBufferSize) end)
|
||||
|
||||
return utf8.tostring(result)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function io_stream:read_line()
|
||||
local result = Bytearray()
|
||||
|
||||
local first = true
|
||||
|
||||
while true do
|
||||
local char = self:__read(1)
|
||||
|
||||
if #char == 0 then
|
||||
if first then return else break end
|
||||
end
|
||||
|
||||
char = char[1]
|
||||
|
||||
if char == LF then break
|
||||
elseif char == CR then
|
||||
char = self:__read(1)
|
||||
|
||||
if char[1] == LF then break
|
||||
else
|
||||
result:append(CR)
|
||||
result:append(char[1])
|
||||
end
|
||||
else result:append(char) end
|
||||
|
||||
first = false
|
||||
end
|
||||
|
||||
return utf8.tostring(result)
|
||||
end
|
||||
|
||||
function io_stream:write_line(str)
|
||||
self:__write(utf8.tobytes(str .. LF))
|
||||
end
|
||||
|
||||
function io_stream:read(arg, useTable)
|
||||
local argType = type(arg)
|
||||
|
||||
if self.binaryMode then
|
||||
local byteArr
|
||||
|
||||
if argType == "number" then
|
||||
-- using 'arg' as length
|
||||
|
||||
byteArr = self:__read(arg)
|
||||
|
||||
if useTable == true then
|
||||
local t = { }
|
||||
|
||||
for i = 1, #byteArr do
|
||||
t[i] = byteArr[i]
|
||||
end
|
||||
|
||||
return t
|
||||
else
|
||||
return byteArr
|
||||
end
|
||||
elseif argType == "string" then
|
||||
return byteutil.unpack(
|
||||
arg,
|
||||
self:__read(byteutil.get_size(arg))
|
||||
)
|
||||
elseif argType == nil then
|
||||
error(
|
||||
"in binary mode the first argument must be a string data format"..
|
||||
" for the library \"byteutil\" or the number of bytes to read"
|
||||
)
|
||||
else
|
||||
error("unknown argument type: "..argType)
|
||||
end
|
||||
else
|
||||
if not arg then
|
||||
return self:read_line()
|
||||
else
|
||||
local linesCount = arg
|
||||
local trimLastEmptyLines = useTable or true
|
||||
|
||||
if linesCount < 0 then error "count of lines to read must be positive" end
|
||||
|
||||
local result = { }
|
||||
|
||||
for i = 1, linesCount do
|
||||
result[i] = self:read_line()
|
||||
end
|
||||
|
||||
if trimLastEmptyLines then
|
||||
local i = #result
|
||||
|
||||
while i >= 0 do
|
||||
local length = utf8.length(result[i])
|
||||
|
||||
if length > 0 then break
|
||||
else result[i] = nil end
|
||||
|
||||
i = i - 1
|
||||
end
|
||||
|
||||
local i = 1
|
||||
|
||||
while #result > 0 do
|
||||
local length = utf8.length(result[i])
|
||||
|
||||
if length > 0 then break
|
||||
else table.remove(result, i) end
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function io_stream:write(arg, ...)
|
||||
local argType = type(arg)
|
||||
|
||||
if self.binaryMode then
|
||||
local byteArr
|
||||
|
||||
if argType ~= "string" then
|
||||
-- using arg as bytes table/bytearray
|
||||
|
||||
if argType == "table" then
|
||||
byteArr = Bytearray(arg)
|
||||
else
|
||||
byteArr = arg
|
||||
end
|
||||
else
|
||||
byteArr = byteutil.pack(arg, ...)
|
||||
end
|
||||
|
||||
self:__write(byteArr)
|
||||
else
|
||||
if argType == "string" then
|
||||
self:write_line(arg)
|
||||
elseif argType == "table" then
|
||||
for i = 1, #arg do
|
||||
self:write_line(arg[i])
|
||||
end
|
||||
else error("unknown argument type: "..argType) end
|
||||
end
|
||||
end
|
||||
|
||||
function io_stream:is_alive()
|
||||
return self.ioLib.is_alive(self.descriptor)
|
||||
end
|
||||
|
||||
function io_stream:is_closed()
|
||||
return not self:is_alive()
|
||||
end
|
||||
|
||||
function io_stream:close()
|
||||
if self.mode == BUFFERED_MODE then
|
||||
self.readBuffer:clear()
|
||||
self.writeBuffer:clear()
|
||||
end
|
||||
|
||||
return self.ioLib.close(self.descriptor)
|
||||
end
|
||||
|
||||
function io_stream:flush()
|
||||
if self.mode == BUFFERED_MODE and #self.writeBuffer > 0 then
|
||||
self.ioLib.write(self.descriptor, self.writeBuffer)
|
||||
self.writeBuffer:clear()
|
||||
end
|
||||
|
||||
if self.flushMode ~= FLUSH_MODE_ONLY_BUFFER then self.ioLib.flush(self.descriptor) end
|
||||
end
|
||||
|
||||
return io_stream
|
||||
@ -317,10 +317,30 @@ entities.get_all = function(uids)
|
||||
return stdcomp.get_all(uids)
|
||||
end
|
||||
end
|
||||
|
||||
local bytearray = require "core:internal/bytearray"
|
||||
|
||||
Bytearray = bytearray.FFIBytearray
|
||||
Bytearray_as_string = bytearray.FFIBytearray_as_string
|
||||
Bytearray_construct = function(...) return Bytearray(...) end
|
||||
|
||||
file.open = require "core:internal/stream_providers/file"
|
||||
file.open_named_pipe = require "core:internal/stream_providers/named_pipe"
|
||||
|
||||
if ffi.os == "Windows" then
|
||||
ffi.cdef[[
|
||||
unsigned long GetCurrentProcessId();
|
||||
]]
|
||||
|
||||
os.pid = ffi.C.GetCurrentProcessId()
|
||||
else
|
||||
ffi.cdef[[
|
||||
int getpid(void);
|
||||
]]
|
||||
|
||||
os.pid = ffi.C.getpid()
|
||||
end
|
||||
|
||||
ffi = nil
|
||||
|
||||
math.randomseed(time.uptime() * 1536227939)
|
||||
@ -473,6 +493,7 @@ function __vc_on_world_quit()
|
||||
_rules.clear()
|
||||
gui_util:__reset_local()
|
||||
stdcomp.__reset()
|
||||
file.__close_all_descriptors()
|
||||
end
|
||||
|
||||
local __vc_coroutines = {}
|
||||
@ -629,4 +650,4 @@ function dofile(path)
|
||||
end
|
||||
end
|
||||
return _dofile(path)
|
||||
end
|
||||
end
|
||||
104
src/logic/scripting/descriptors_manager.cpp
Normal file
104
src/logic/scripting/descriptors_manager.cpp
Normal file
@ -0,0 +1,104 @@
|
||||
#include "logic/scripting/descriptors_manager.hpp"
|
||||
|
||||
#include "debug/Logger.hpp"
|
||||
|
||||
static debug::Logger logger("descriptors-manager");
|
||||
|
||||
namespace scripting {
|
||||
|
||||
std::vector<std::optional<StreamDescriptor>> descriptors_manager::descriptors;
|
||||
|
||||
std::istream* descriptors_manager::get_input(int descriptor) {
|
||||
if (!is_readable(descriptor))
|
||||
return nullptr;
|
||||
|
||||
return descriptors[descriptor]->in.get();
|
||||
}
|
||||
|
||||
std::ostream* descriptors_manager::get_output(int descriptor) {
|
||||
if (!is_writeable(descriptor))
|
||||
return nullptr;
|
||||
|
||||
return descriptors[descriptor]->out.get();
|
||||
}
|
||||
|
||||
void descriptors_manager::flush(int descriptor) {
|
||||
if (is_writeable(descriptor)) {
|
||||
descriptors[descriptor]->out->flush();
|
||||
}
|
||||
}
|
||||
|
||||
bool descriptors_manager::has_descriptor(int descriptor) {
|
||||
return is_readable(descriptor) || is_writeable(descriptor);
|
||||
}
|
||||
|
||||
bool descriptors_manager::is_readable(int descriptor) {
|
||||
return descriptor >= 0 && descriptor < static_cast<int>(descriptors.size())
|
||||
&& descriptors[descriptor].has_value()
|
||||
&& descriptors[descriptor]->in != nullptr;
|
||||
}
|
||||
|
||||
bool descriptors_manager::is_writeable(int descriptor) {
|
||||
return descriptor >= 0 && descriptor < static_cast<int>(descriptors.size())
|
||||
&& descriptors[descriptor].has_value()
|
||||
&& descriptors[descriptor]->out != nullptr;
|
||||
}
|
||||
|
||||
void descriptors_manager::close(int descriptor) {
|
||||
if (descriptor >= 0 && descriptor < static_cast<int>(descriptors.size())) {
|
||||
if (descriptors[descriptor].has_value()) {
|
||||
auto& desc = descriptors[descriptor].value();
|
||||
|
||||
if (desc.out)
|
||||
desc.out->flush();
|
||||
|
||||
desc.in.reset();
|
||||
desc.out.reset();
|
||||
}
|
||||
|
||||
descriptors[descriptor].reset();
|
||||
|
||||
descriptors[descriptor] = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
int descriptors_manager::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 descriptors_manager::close_all_descriptors() {
|
||||
for (int i = 0; i < static_cast<int>(descriptors.size()); ++i) {
|
||||
if (descriptors[i].has_value()) {
|
||||
close(i);
|
||||
}
|
||||
}
|
||||
|
||||
descriptors.clear();
|
||||
}
|
||||
}
|
||||
39
src/logic/scripting/descriptors_manager.hpp
Normal file
39
src/logic/scripting/descriptors_manager.hpp
Normal file
@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <istream>
|
||||
#include <ostream>
|
||||
|
||||
#include "io/io.hpp"
|
||||
|
||||
namespace scripting {
|
||||
|
||||
struct StreamDescriptor {
|
||||
std::unique_ptr<std::istream> in;
|
||||
std::unique_ptr<std::ostream> out;
|
||||
};
|
||||
|
||||
class descriptors_manager {
|
||||
private:
|
||||
static std::vector<std::optional<StreamDescriptor>> descriptors;
|
||||
|
||||
public:
|
||||
static std::istream* get_input(int descriptor);
|
||||
static std::ostream* get_output(int descriptor);
|
||||
|
||||
static void flush(int descriptor);
|
||||
|
||||
static bool has_descriptor(int descriptor);
|
||||
|
||||
static bool is_readable(int descriptor);
|
||||
static bool is_writeable(int descriptor);
|
||||
|
||||
static void close(int descriptor);
|
||||
static int open_descriptor(const io::path& path, bool write, bool read);
|
||||
|
||||
static void close_all_descriptors();
|
||||
};
|
||||
}
|
||||
@ -197,9 +197,16 @@ static int l_tpack(lua::State* L) {
|
||||
return pack(L, format, true);
|
||||
}
|
||||
|
||||
static int l_get_size(lua::State* L) {
|
||||
return lua::pushinteger(
|
||||
L, static_cast<int>(calc_size(lua::require_string(L, 1)))
|
||||
);
|
||||
}
|
||||
|
||||
const luaL_Reg byteutillib[] = {
|
||||
{"pack", lua::wrap<l_pack>},
|
||||
{"tpack", lua::wrap<l_tpack>},
|
||||
{"unpack", lua::wrap<l_unpack>},
|
||||
{"get_size", lua::wrap<l_get_size>},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
#include "util/stringutil.hpp"
|
||||
#include "api_lua.hpp"
|
||||
#include "../lua_engine.hpp"
|
||||
#include "logic/scripting/descriptors_manager.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using namespace scripting;
|
||||
@ -258,6 +259,149 @@ static int l_create_zip(lua::State* L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_open_descriptor(lua::State* L) {
|
||||
io::path path = lua::require_string(L, 1);
|
||||
auto mode = lua::require_lstring(L, 2);
|
||||
|
||||
bool write = mode.find('w') != std::string::npos;
|
||||
bool read = mode.find('r') != std::string::npos;
|
||||
|
||||
if (write && !is_writeable(path.entryPoint())) {
|
||||
throw std::runtime_error("access denied");
|
||||
}
|
||||
|
||||
if(!write && !read) {
|
||||
throw std::runtime_error("mode must contain read or write flag");
|
||||
}
|
||||
|
||||
if(write && read) {
|
||||
throw std::runtime_error("random access file i/o is not supported");
|
||||
}
|
||||
|
||||
bool wplusMode = write && mode.find('+') != std::string::npos;
|
||||
|
||||
std::vector<char> buffer;
|
||||
|
||||
if(wplusMode) {
|
||||
int temp_descriptor = scripting::descriptors_manager::open_descriptor(path, false, true);
|
||||
|
||||
if (temp_descriptor == -1) {
|
||||
throw std::runtime_error("failed to open descriptor for initial reading");
|
||||
}
|
||||
|
||||
auto* in_stream = scripting::descriptors_manager::get_input(temp_descriptor);
|
||||
|
||||
in_stream->seekg(0, std::ios::end);
|
||||
std::streamsize size = in_stream->tellg();
|
||||
in_stream->seekg(0, std::ios::beg);
|
||||
|
||||
buffer.resize(size);
|
||||
in_stream->read(buffer.data(), size);
|
||||
|
||||
scripting::descriptors_manager::close(temp_descriptor);
|
||||
}
|
||||
|
||||
int descriptor = scripting::descriptors_manager::open_descriptor(path, write, read);
|
||||
|
||||
if(descriptor == -1) {
|
||||
throw std::runtime_error("failed to open descriptor");
|
||||
}
|
||||
|
||||
if(wplusMode) {
|
||||
auto* out_stream = scripting::descriptors_manager::get_output(descriptor);
|
||||
out_stream->write(buffer.data(), buffer.size());
|
||||
out_stream->flush();
|
||||
}
|
||||
|
||||
return lua::pushinteger(L, descriptor);
|
||||
}
|
||||
|
||||
static int l_has_descriptor(lua::State* L) {
|
||||
return lua::pushboolean(L, scripting::descriptors_manager::has_descriptor(lua::tointeger(L, 1)));
|
||||
}
|
||||
|
||||
static int l_read_descriptor(lua::State* L) {
|
||||
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);
|
||||
|
||||
auto* stream = scripting::descriptors_manager::get_input(descriptor);
|
||||
|
||||
util::Buffer<char> buffer(maxlen);
|
||||
|
||||
stream->read(buffer.data(), maxlen);
|
||||
|
||||
std::streamsize read_len = stream->gcount();
|
||||
|
||||
return lua::create_bytearray(L, buffer.data(), read_len);
|
||||
}
|
||||
|
||||
static int l_write_descriptor(lua::State* L) {
|
||||
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* stream = scripting::descriptors_manager::get_output(descriptor);
|
||||
|
||||
stream->write(data.data(), static_cast<std::streamsize>(data.size()));
|
||||
|
||||
if (!stream->good()) {
|
||||
throw std::runtime_error("failed to write to stream");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_flush_descriptor(lua::State* L) {
|
||||
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");
|
||||
}
|
||||
|
||||
scripting::descriptors_manager::flush(descriptor);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_close_descriptor(lua::State* L) {
|
||||
int descriptor = lua::tointeger(L, 1);
|
||||
|
||||
if (!scripting::descriptors_manager::has_descriptor(descriptor)) {
|
||||
throw std::runtime_error("unknown descriptor");
|
||||
}
|
||||
|
||||
scripting::descriptors_manager::close(descriptor);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_close_all_descriptors(lua::State* L) {
|
||||
scripting::descriptors_manager::close_all_descriptors();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const luaL_Reg filelib[] = {
|
||||
{"exists", lua::wrap<l_exists>},
|
||||
{"find", lua::wrap<l_find>},
|
||||
@ -283,5 +427,12 @@ const luaL_Reg filelib[] = {
|
||||
{"mount", lua::wrap<l_mount>},
|
||||
{"unmount", lua::wrap<l_unmount>},
|
||||
{"create_zip", lua::wrap<l_create_zip>},
|
||||
{"__open_descriptor", lua::wrap<l_open_descriptor>},
|
||||
{"__has_descriptor", lua::wrap<l_has_descriptor>},
|
||||
{"__read_descriptor", lua::wrap<l_read_descriptor>},
|
||||
{"__write_descriptor", lua::wrap<l_write_descriptor>},
|
||||
{"__flush_descriptor", lua::wrap<l_flush_descriptor>},
|
||||
{"__close_descriptor", lua::wrap<l_close_descriptor>},
|
||||
{"__close_all_descriptors", lua::wrap<l_close_all_descriptors>},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
@ -1,8 +1,13 @@
|
||||
#include "engine/Engine.hpp"
|
||||
#include "api_lua.hpp"
|
||||
#include <ctime>
|
||||
|
||||
using namespace scripting;
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#define USE_MSVC_TIME_SAFE
|
||||
#endif
|
||||
|
||||
static int l_uptime(lua::State* L) {
|
||||
return lua::pushnumber(L, engine->getTime().getTime());
|
||||
}
|
||||
@ -11,8 +16,57 @@ static int l_delta(lua::State* L) {
|
||||
return lua::pushnumber(L, engine->getTime().getDelta());
|
||||
}
|
||||
|
||||
static int l_utc_time(lua::State* L) {
|
||||
return lua::pushnumber(L, std::time(nullptr));
|
||||
}
|
||||
|
||||
static int l_local_time(lua::State* L) {
|
||||
std::time_t t = std::time(nullptr);
|
||||
|
||||
std::tm gmt_tm{};
|
||||
std::tm local_tm{};
|
||||
|
||||
#if defined(USE_MSVC_TIME_SAFE)
|
||||
gmtime_s(&gmt_tm, &t);
|
||||
localtime_s(&local_tm, &t);
|
||||
#else
|
||||
gmtime_r(&t, &gmt_tm);
|
||||
localtime_r(&t, &local_tm);
|
||||
#endif
|
||||
|
||||
std::time_t utc_time = std::mktime(&gmt_tm);
|
||||
std::time_t local_time = std::mktime(&local_tm);
|
||||
std::time_t offset = local_time - utc_time;
|
||||
|
||||
return lua::pushnumber(L, t + offset);
|
||||
}
|
||||
|
||||
static int l_utc_offset(lua::State* L) {
|
||||
std::time_t t = std::time(nullptr);
|
||||
|
||||
std::tm gmt_tm{};
|
||||
std::tm local_tm{};
|
||||
|
||||
#if defined(USE_MSVC_TIME_SAFE)
|
||||
gmtime_s(&gmt_tm, &t);
|
||||
localtime_s(&local_tm, &t);
|
||||
#else
|
||||
gmtime_r(&t, &gmt_tm);
|
||||
localtime_r(&t, &local_tm);
|
||||
#endif
|
||||
|
||||
std::time_t utc_time = std::mktime(&gmt_tm);
|
||||
std::time_t local_time = std::mktime(&local_tm);
|
||||
std::time_t offset = local_time - utc_time;
|
||||
|
||||
return lua::pushnumber(L, offset);
|
||||
}
|
||||
|
||||
const luaL_Reg timelib[] = {
|
||||
{"uptime", lua::wrap<l_uptime>},
|
||||
{"delta", lua::wrap<l_delta>},
|
||||
{"utc_time", lua::wrap<l_utc_time>},
|
||||
{"utc_offset", lua::wrap<l_utc_offset>},
|
||||
{"local_time", lua::wrap<l_local_time>},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user