Merge branch 'dev' into debugging-client
This commit is contained in:
commit
203862e206
2
.github/workflows/appimage.yml
vendored
2
.github/workflows/appimage.yml
vendored
@ -2,7 +2,7 @@ name: x86-64 AppImage
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "main", "dev", "release-**"]
|
branches: [ "main", "release-**"]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ "main", "dev" ]
|
branches: [ "main", "dev" ]
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/macos.yml
vendored
2
.github/workflows/macos.yml
vendored
@ -2,7 +2,7 @@ name: Macos Build
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "main", "dev", "release-**"]
|
branches: [ "main", "release-**"]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ "main", "dev" ]
|
branches: [ "main", "dev" ]
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/windows-clang.yml
vendored
2
.github/workflows/windows-clang.yml
vendored
@ -2,7 +2,7 @@ name: Windows Build (CLang)
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "main", "dev", "release-**"]
|
branches: [ "main", "release-**"]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ "main", "dev" ]
|
branches: [ "main", "dev" ]
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/windows.yml
vendored
2
.github/workflows/windows.yml
vendored
@ -2,7 +2,7 @@ name: MSVC Build
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "main", "dev", "release-**"]
|
branches: [ "main", "release-**"]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ "main", "dev" ]
|
branches: [ "main", "dev" ]
|
||||||
|
|
||||||
|
|||||||
@ -203,3 +203,79 @@ audio.count_speakers() -> integer
|
|||||||
-- get current number of playing streams
|
-- get current number of playing streams
|
||||||
audio.count_streams() -> integer
|
audio.count_streams() -> integer
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### audio.PCMStream
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- Creating a PCM data source
|
||||||
|
local stream = audio.PCMStream(
|
||||||
|
-- Sample rate
|
||||||
|
sample_rate: integer,
|
||||||
|
-- Number of channels (1 - mono, 2 - stereo)
|
||||||
|
channels: integer,
|
||||||
|
-- Number of bits per sample (8 or 16)
|
||||||
|
bits_per_sample: integer,
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Feeding PCM data into the stream
|
||||||
|
stream:feed(
|
||||||
|
-- PCM data to be fed into the stream
|
||||||
|
data: Bytearray
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Publishing the PCM data source for use by the engine systems
|
||||||
|
stream:share(
|
||||||
|
-- Alias of the audio stream, which can be referenced in audio.play_stream
|
||||||
|
alias: string
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Creating a sound from the PCM data in the stream
|
||||||
|
stream:create_sound(
|
||||||
|
-- Name of the created sound
|
||||||
|
name: string
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Audio Recording
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- Requests access to audio recording
|
||||||
|
-- On confirmation, the callback receives a token for use in audio.input.fetch
|
||||||
|
audio.input.request_open(callback: function(string))
|
||||||
|
|
||||||
|
-- Reads new PCM audio input data
|
||||||
|
audio.input.fetch(
|
||||||
|
-- Token obtained through audio.input.request_open
|
||||||
|
access_token: string,
|
||||||
|
-- Maximum buffer size in bytes (optional)
|
||||||
|
[optional] max_read_size: integer
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example of Audio Generation:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- For working with 16-bit samples, use a U16view over Bytearray
|
||||||
|
-- Example:
|
||||||
|
local max_amplitude = 32767
|
||||||
|
local sample_rate = 44100
|
||||||
|
local total_samples = sample_rate * 5 -- 5 seconds of mono
|
||||||
|
local bytes = Bytearray(total_samples * 2) -- 5 seconds of 16-bit mono
|
||||||
|
local samples = I16view(bytes)
|
||||||
|
|
||||||
|
local frequency_hz = 400
|
||||||
|
for i=1, total_samples do
|
||||||
|
local value = math.sin(i * math.pi * 2 / sample_rate * frequency_hz)
|
||||||
|
samples[i] = value * max_amplitude
|
||||||
|
end
|
||||||
|
|
||||||
|
local stream_name = "test-stream"
|
||||||
|
local stream = audio.PCMStream(sample_rate, 1, 16)
|
||||||
|
stream:feed(bytes)
|
||||||
|
stream:share(stream_name)
|
||||||
|
|
||||||
|
local volume = 1.0
|
||||||
|
local pitch = 1.0
|
||||||
|
local channel = "ui"
|
||||||
|
audio.play_stream_2d(stream_name, volume, pitch, channel)
|
||||||
|
```
|
||||||
|
|||||||
@ -196,6 +196,10 @@ Here, *color* can be specified in the following ways:
|
|||||||
| data:update() | applies changes to the canvas and uploads it to the GPU |
|
| data:update() | applies changes to the canvas and uploads it to the GPU |
|
||||||
| data:set_data(data: table<int>) | replaces pixel data (width * height * 4 numbers) |
|
| data:set_data(data: table<int>) | replaces pixel data (width * height * 4 numbers) |
|
||||||
| data:create_texture(name: str) | creates and shares texture to renderer |
|
| data:create_texture(name: str) | creates and shares texture to renderer |
|
||||||
|
| data:unbind_texture() | unbinds the texture from the canvas |
|
||||||
|
| data:mul(*color* or Canvas) | multiplies a color by the specified color or canvas |
|
||||||
|
| data:add(*color* or Canvas) | adds a color or another canvas to a color |
|
||||||
|
| data:sub(*color* or Canvas) | subtracts a color or another canvas to a color |
|
||||||
|
|
||||||
## Inline frame (iframe)
|
## Inline frame (iframe)
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,7 @@
|
|||||||
Доступ к спикерам производится по целочисленным id, которые не повторяются за время работы движка, следует избегать хранения прямых указателей на объекты класса.
|
Доступ к спикерам производится по целочисленным id, которые не повторяются за время работы движка, следует избегать хранения прямых указателей на объекты класса.
|
||||||
|
|
||||||
Нумерация ID спикеров начинается с 1. ID 0 означает невозможность воспроизведения, по какой-либо причине.
|
Нумерация ID спикеров начинается с 1. ID 0 означает невозможность воспроизведения, по какой-либо причине.
|
||||||
|
|
||||||
### Звук (Sound)
|
### Звук (Sound)
|
||||||
|
|
||||||
Звуковые данные загруженные в память для возможности одновременного воспроизведения из нескольких источников. Может предоставлять доступ к PCM данным.
|
Звуковые данные загруженные в память для возможности одновременного воспроизведения из нескольких источников. Может предоставлять доступ к PCM данным.
|
||||||
@ -203,3 +204,79 @@ audio.count_speakers() -> integer
|
|||||||
-- получить текущее число проигрываемых аудио-потоков
|
-- получить текущее число проигрываемых аудио-потоков
|
||||||
audio.count_streams() -> integer
|
audio.count_streams() -> integer
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### audio.PCMStream
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- создание источника PCM данных
|
||||||
|
local stream = audio.PCMStream(
|
||||||
|
-- частота дискретизации
|
||||||
|
sample_rate: integer,
|
||||||
|
-- число каналов (1 - моно, 2 - стерео)
|
||||||
|
channels: integer,
|
||||||
|
-- число бит на сэмпл (8 или 16)
|
||||||
|
bits_per_sample: integer,
|
||||||
|
)
|
||||||
|
|
||||||
|
-- подача PCM данных в поток
|
||||||
|
stream:feed(
|
||||||
|
-- PCM данные для подачи в поток
|
||||||
|
data: Bytearray
|
||||||
|
)
|
||||||
|
|
||||||
|
-- публикация источника PCM данных для использования системами движка
|
||||||
|
stream:share(
|
||||||
|
-- имя потокового аудио, которое можно будет указать в audio.play_stream
|
||||||
|
alias: string
|
||||||
|
)
|
||||||
|
|
||||||
|
-- создание звука из имеющихся в потоке PCM данных
|
||||||
|
stream:create_sound(
|
||||||
|
-- имя создаваемого звука
|
||||||
|
name: string
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Запись звука
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- запрашивает доступ к записи звука
|
||||||
|
-- при подтверждении, в callback передаётся токен для использовании в audio.input.fetch
|
||||||
|
audio.input.request_open(callback: function(string))
|
||||||
|
|
||||||
|
-- читает новые PCM данные аудио ввода
|
||||||
|
audio.input.fetch(
|
||||||
|
-- токен, полученный через audio.input.request_open
|
||||||
|
access_token: string,
|
||||||
|
-- максимальное размер буфера в байтах
|
||||||
|
[опционально] max_read_size: integer
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Пример генерации аудио:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- для работы с 16-битными семплами используйте U16view поверх Bytearray
|
||||||
|
-- пример:
|
||||||
|
local max_amplitude = 32767
|
||||||
|
local sample_rate = 44100
|
||||||
|
local total_samples = sample_rate * 5 -- 5 секунд моно
|
||||||
|
local bytes = Bytearray(total_samples * 2) -- 5 секунд 16 бит моно
|
||||||
|
local samples = I16view(bytes)
|
||||||
|
|
||||||
|
local frequency_hz = 400
|
||||||
|
for i=1, total_samples do
|
||||||
|
local value = math.sin(i * math.pi * 2 / sample_rate * frequency_hz)
|
||||||
|
samples[i] = value * max_amplitude
|
||||||
|
end
|
||||||
|
|
||||||
|
local stream_name = "test-stream"
|
||||||
|
local stream = audio.PCMStream(sample_rate, 1, 16)
|
||||||
|
stream:feed(bytes)
|
||||||
|
stream:share(stream_name)
|
||||||
|
|
||||||
|
local volume = 1.0
|
||||||
|
local pitch = 1.0
|
||||||
|
local channel = "ui"
|
||||||
|
audio.play_stream_2d(stream_name, volume, pitch, channel)
|
||||||
|
```
|
||||||
|
|||||||
@ -196,6 +196,10 @@ document["worlds-panel"]:clear()
|
|||||||
| data:update() | применяет изменения и загружает холст в видеопамять |
|
| data:update() | применяет изменения и загружает холст в видеопамять |
|
||||||
| data:set_data(data: table<int>) | заменяет данные пикселей (ширина * высота * 4 чисел) |
|
| data:set_data(data: table<int>) | заменяет данные пикселей (ширина * высота * 4 чисел) |
|
||||||
| data:create_texture(name: str) | создаёт и делится текстурой с рендерером |
|
| data:create_texture(name: str) | создаёт и делится текстурой с рендерером |
|
||||||
|
| data:unbind_texture() | отвязывает текстуру от холста |
|
||||||
|
| data:mul(*цвет* или Canvas) | умножает увет на указанный цвет или холст |
|
||||||
|
| data:add(*цвет* или Canvas) | прибавляет цвет или другой холст к цвету |
|
||||||
|
| data:sub(*цвет* или Canvas) | вычитает цвет или другой холст к цвету |
|
||||||
|
|
||||||
## Рамка встраивания (iframe)
|
## Рамка встраивания (iframe)
|
||||||
|
|
||||||
|
|||||||
@ -24,10 +24,46 @@ function update_setting(x, id, name, postfix)
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local initialized = false
|
||||||
|
|
||||||
function on_open()
|
function on_open()
|
||||||
|
if not initialized then
|
||||||
|
initialized = true
|
||||||
|
local token = audio.input.__get_core_token()
|
||||||
|
document.root:add("<container id='tm' />")
|
||||||
|
local prev_amplitude = 0.0
|
||||||
|
document.tm:setInterval(16, function()
|
||||||
|
audio.input.fetch(token)
|
||||||
|
local amplitude = audio.input.get_max_amplitude()
|
||||||
|
if amplitude > 0.0 then
|
||||||
|
amplitude = math.sqrt(amplitude)
|
||||||
|
end
|
||||||
|
amplitude = math.max(amplitude, prev_amplitude - time.delta())
|
||||||
|
document.input_volume_inner.size = {
|
||||||
|
prev_amplitude *
|
||||||
|
document.input_volume_outer.size[1],
|
||||||
|
document.input_volume_outer.size[2]
|
||||||
|
}
|
||||||
|
prev_amplitude = amplitude
|
||||||
|
end)
|
||||||
|
end
|
||||||
create_setting("audio.volume-master", "Master Volume", 0.01)
|
create_setting("audio.volume-master", "Master Volume", 0.01)
|
||||||
create_setting("audio.volume-regular", "Regular Sounds", 0.01)
|
create_setting("audio.volume-regular", "Regular Sounds", 0.01)
|
||||||
create_setting("audio.volume-ui", "UI Sounds", 0.01)
|
create_setting("audio.volume-ui", "UI Sounds", 0.01)
|
||||||
create_setting("audio.volume-ambient", "Ambient", 0.01)
|
create_setting("audio.volume-ambient", "Ambient", 0.01)
|
||||||
create_setting("audio.volume-music", "Music", 0.01)
|
create_setting("audio.volume-music", "Music", 0.01)
|
||||||
|
document.root:add("<label context='settings'>@Microphone</label>")
|
||||||
|
document.root:add("<select id='input_device_select' "..
|
||||||
|
"onselect='function(opt) audio.set_input_device(opt) end'/>")
|
||||||
|
document.root:add("<container id='input_volume_outer' color='#000000' size='4'>"
|
||||||
|
.."<container id='input_volume_inner' color='#00FF00FF' pos='1' size='2'/>"
|
||||||
|
.."</container>")
|
||||||
|
local selectbox = document.input_device_select
|
||||||
|
local devices = {}
|
||||||
|
local names = audio.get_input_devices_names()
|
||||||
|
for i, name in ipairs(names) do
|
||||||
|
table.insert(devices, {value=name, text=name})
|
||||||
|
end
|
||||||
|
selectbox.options = devices
|
||||||
|
selectbox.value = audio.get_input_info().device_specifier
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<container id="%{id}" size="32" tooltip="%{text}"
|
<container id="%{id}" size="32" tooltip="%{text}"
|
||||||
onclick="events.emit('core:open_traceback', '%{traceback}')">
|
onclick="events.emit('core:open_traceback', '%{traceback}')">
|
||||||
<image src="gui/%{type}" size="32"/>
|
<image src="gui/%{type}" size="32"/>
|
||||||
<label pos="36,2" sizefunc="-1,-1">%{text}</label>
|
<label pos="36,2" size-func="-1,-1">%{text}</label>
|
||||||
<image src="gui/cross" interactive="true" size="16" gravity="top-right"
|
<image src="gui/cross" interactive="true" size="16" gravity="top-right"
|
||||||
onclick="document['%{id}']:destruct()"></image>
|
onclick="document['%{id}']:destruct()"></image>
|
||||||
</container>
|
</container>
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
onclick='%{open_func}("%{filename}")'
|
onclick='%{open_func}("%{filename}")'
|
||||||
markup='md'
|
markup='md'
|
||||||
tooltip='%{unit}'
|
tooltip='%{unit}'
|
||||||
sizefunc="-1,-1">
|
size-func="-1,-1">
|
||||||
[#FFFFFF80]%{path}[#FFFFFFFF]%{name}
|
[#FFFFFF80]%{path}[#FFFFFFFF]%{name}
|
||||||
</label>
|
</label>
|
||||||
</container>
|
</container>
|
||||||
|
|||||||
63
res/modules/internal/audio_input.lua
Normal file
63
res/modules/internal/audio_input.lua
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
local _base64_encode_urlsafe = base64.encode_urlsafe
|
||||||
|
local _random_bytes = random.bytes
|
||||||
|
local core_token = _base64_encode_urlsafe(_random_bytes(18))
|
||||||
|
|
||||||
|
local audio_input_tokens_store = {[core_token] = "core"}
|
||||||
|
audio.input = {}
|
||||||
|
|
||||||
|
local _gui_confirm = gui.confirm
|
||||||
|
local _debug_pack_by_frame = debug.get_pack_by_frame
|
||||||
|
local _audio_fetch_input = audio.__fetch_input
|
||||||
|
audio.__fetch_input = nil
|
||||||
|
local MAX_FETCH = 44100 * 4
|
||||||
|
local MAX_AMPLITUDE = 32768
|
||||||
|
|
||||||
|
local total_fetch = Bytearray()
|
||||||
|
local max_amplitude = 0.0
|
||||||
|
|
||||||
|
function audio.__reset_fetch_buffer()
|
||||||
|
total_fetch:clear()
|
||||||
|
max_amplitude = 0.0
|
||||||
|
end
|
||||||
|
|
||||||
|
function audio.input.get_max_amplitude()
|
||||||
|
return max_amplitude / MAX_AMPLITUDE
|
||||||
|
end
|
||||||
|
|
||||||
|
function audio.input.fetch(token, size)
|
||||||
|
size = size or MAX_FETCH
|
||||||
|
if audio_input_tokens_store[token] then
|
||||||
|
if #total_fetch >= size then
|
||||||
|
return total_fetch:slice(1, size)
|
||||||
|
end
|
||||||
|
local fetched = _audio_fetch_input(size - #total_fetch)
|
||||||
|
if not fetched then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
for i, sample in ipairs(I16view(fetched)) do
|
||||||
|
max_amplitude = math.max(math.abs(sample))
|
||||||
|
end
|
||||||
|
total_fetch:append(fetched)
|
||||||
|
return total_fetch:slice()
|
||||||
|
end
|
||||||
|
error("access denied")
|
||||||
|
end
|
||||||
|
|
||||||
|
local GRANT_PERMISSION_MSG = "Grant '%{0}' pack audio recording permission?"
|
||||||
|
|
||||||
|
function audio.input.request_open(callback)
|
||||||
|
local token = _base64_encode_urlsafe(_random_bytes(18))
|
||||||
|
local caller = _debug_pack_by_frame(1)
|
||||||
|
_gui_confirm(gui.str(GRANT_PERMISSION_MSG):gsub("%%{0}", caller), function()
|
||||||
|
audio_input_tokens_store[token] = caller
|
||||||
|
callback(token)
|
||||||
|
menu:reset()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function audio.input.__get_core_token()
|
||||||
|
local caller = _debug_pack_by_frame(1)
|
||||||
|
if caller == "core" then
|
||||||
|
return core_token
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -14,6 +14,8 @@ FFI.cdef[[
|
|||||||
|
|
||||||
local malloc = FFI.C.malloc
|
local malloc = FFI.C.malloc
|
||||||
local free = FFI.C.free
|
local free = FFI.C.free
|
||||||
|
local FFIBytearray
|
||||||
|
local bytearray_type
|
||||||
|
|
||||||
local function grow_buffer(self, elems)
|
local function grow_buffer(self, elems)
|
||||||
local new_capacity = math.ceil(self.capacity / 0.75 + elems)
|
local new_capacity = math.ceil(self.capacity / 0.75 + elems)
|
||||||
@ -119,6 +121,23 @@ local function get_capacity(self)
|
|||||||
return self.capacity
|
return self.capacity
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function slice(self, offset, length)
|
||||||
|
offset = offset or 1
|
||||||
|
length = length or (self.size - offset + 1)
|
||||||
|
if offset < 1 or offset > self.size then
|
||||||
|
return FFIBytearray(0)
|
||||||
|
end
|
||||||
|
if offset + length - 1 > self.size then
|
||||||
|
length = self.size - offset + 1
|
||||||
|
end
|
||||||
|
local buffer = malloc(length)
|
||||||
|
if not buffer then
|
||||||
|
error("malloc(" .. length .. ") returned NULL")
|
||||||
|
end
|
||||||
|
FFI.copy(buffer, self.bytes + (offset - 1), length)
|
||||||
|
return bytearray_type(buffer, length, length)
|
||||||
|
end
|
||||||
|
|
||||||
local bytearray_methods = {
|
local bytearray_methods = {
|
||||||
append=append,
|
append=append,
|
||||||
insert=insert,
|
insert=insert,
|
||||||
@ -127,6 +146,7 @@ local bytearray_methods = {
|
|||||||
clear=clear,
|
clear=clear,
|
||||||
reserve=reserve,
|
reserve=reserve,
|
||||||
get_capacity=get_capacity,
|
get_capacity=get_capacity,
|
||||||
|
slice=slice,
|
||||||
}
|
}
|
||||||
|
|
||||||
local bytearray_mt = {
|
local bytearray_mt = {
|
||||||
@ -168,9 +188,9 @@ local bytearray_mt = {
|
|||||||
}
|
}
|
||||||
bytearray_mt.__pairs = bytearray_mt.__ipairs
|
bytearray_mt.__pairs = bytearray_mt.__ipairs
|
||||||
|
|
||||||
local bytearray_type = FFI.metatype("bytearray_t", bytearray_mt)
|
bytearray_type = FFI.metatype("bytearray_t", bytearray_mt)
|
||||||
|
|
||||||
local FFIBytearray = {
|
FFIBytearray = {
|
||||||
__call = function (self, n)
|
__call = function (self, n)
|
||||||
local t = type(n)
|
local t = type(n)
|
||||||
if t == "string" then
|
if t == "string" then
|
||||||
@ -210,7 +230,59 @@ local function FFIBytearray_as_string(bytes)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function create_FFIview_class(name, typename, typesize)
|
||||||
|
local FFIU16view_mt = {
|
||||||
|
__index = function(self, key)
|
||||||
|
if key <= 0 or key > self.size then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
return self.ptr[key - 1]
|
||||||
|
end,
|
||||||
|
__newindex = function(self, key, value)
|
||||||
|
if key == self.size + 1 then
|
||||||
|
return append(self, value)
|
||||||
|
elseif key <= 0 or key > self.size then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self.ptr[key - 1] = value
|
||||||
|
end,
|
||||||
|
__len = function(self)
|
||||||
|
return self.size
|
||||||
|
end,
|
||||||
|
__tostring = function(self)
|
||||||
|
return string.format(name .. "[%s]{...}", tonumber(self.size))
|
||||||
|
end,
|
||||||
|
__ipairs = function(self)
|
||||||
|
local i = 0
|
||||||
|
return function()
|
||||||
|
i = i + 1
|
||||||
|
if i <= self.size then
|
||||||
|
return i, self.ptr[i - 1]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}
|
||||||
|
return function (bytes)
|
||||||
|
local ptr = FFI.cast(typename .. "*", bytes.bytes)
|
||||||
|
local x = setmetatable({
|
||||||
|
bytes=bytes,
|
||||||
|
ptr=ptr,
|
||||||
|
size=math.floor(bytes.size / typesize),
|
||||||
|
}, FFIU16view_mt)
|
||||||
|
return x
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local FFII16view = create_FFIview_class("FFII16view", "int16_t", 2)
|
||||||
|
local FFIU16view = create_FFIview_class("FFIU16view", "uint16_t", 2)
|
||||||
|
local FFII32view = create_FFIview_class("FFII32view", "int32_t", 4)
|
||||||
|
local FFIU32view = create_FFIview_class("FFIU32view", "uint32_t", 4)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
FFIBytearray = setmetatable(FFIBytearray, FFIBytearray),
|
FFIBytearray = setmetatable(FFIBytearray, FFIBytearray),
|
||||||
FFIBytearray_as_string = FFIBytearray_as_string
|
FFIBytearray_as_string = FFIBytearray_as_string,
|
||||||
|
FFIU16view = FFIU16view,
|
||||||
|
FFII16view = FFII16view,
|
||||||
|
FFIU32view = FFIU32view,
|
||||||
|
FFII32view = FFII32view,
|
||||||
}
|
}
|
||||||
|
|||||||
146
res/modules/internal/debugging.lua
Normal file
146
res/modules/internal/debugging.lua
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
local breakpoints = {}
|
||||||
|
local dbg_steps_mode = false
|
||||||
|
local dbg_step_into_func = false
|
||||||
|
local hook_lock = false
|
||||||
|
local current_func
|
||||||
|
local current_func_stack_size
|
||||||
|
|
||||||
|
local __parse_path = parse_path
|
||||||
|
local _debug_getinfo = debug.getinfo
|
||||||
|
local _debug_getlocal = debug.getlocal
|
||||||
|
local __pause = debug.pause
|
||||||
|
local __error = error
|
||||||
|
local __sethook = debug.sethook
|
||||||
|
|
||||||
|
-- 'return' hook not called for some functions
|
||||||
|
-- todo: speedup
|
||||||
|
local function calc_stack_size()
|
||||||
|
local s = debug.traceback("", 2)
|
||||||
|
local count = 0
|
||||||
|
for i in s:gmatch("\n") do
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
local is_debugging = debug.is_debugging()
|
||||||
|
if is_debugging then
|
||||||
|
__sethook(function (e, line)
|
||||||
|
if e == "return" then
|
||||||
|
local info = _debug_getinfo(2)
|
||||||
|
if info.func == current_func then
|
||||||
|
current_func = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if dbg_steps_mode and not hook_lock then
|
||||||
|
hook_lock = true
|
||||||
|
|
||||||
|
if not dbg_step_into_func then
|
||||||
|
local func = _debug_getinfo(2).func
|
||||||
|
if func ~= current_func then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if current_func_stack_size ~= calc_stack_size() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
current_func = func
|
||||||
|
__pause("step")
|
||||||
|
debug.pull_events()
|
||||||
|
end
|
||||||
|
hook_lock = false
|
||||||
|
local bps = breakpoints[line]
|
||||||
|
if not bps then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local source = _debug_getinfo(2).source
|
||||||
|
if not bps[source] then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
current_func = _debug_getinfo(2).func
|
||||||
|
current_func_stack_size = calc_stack_size()
|
||||||
|
__pause("breakpoint")
|
||||||
|
debug.pull_events()
|
||||||
|
end, "lr")
|
||||||
|
end
|
||||||
|
|
||||||
|
local DBG_EVENT_SET_BREAKPOINT = 1
|
||||||
|
local DBG_EVENT_RM_BREAKPOINT = 2
|
||||||
|
local DBG_EVENT_STEP = 3
|
||||||
|
local DBG_EVENT_STEP_INTO_FUNCTION = 4
|
||||||
|
local DBG_EVENT_RESUME = 5
|
||||||
|
local DBG_EVENT_GET_VALUE = 6
|
||||||
|
local __pull_events = debug.__pull_events
|
||||||
|
local __sendvalue = debug.__sendvalue
|
||||||
|
debug.__pull_events = nil
|
||||||
|
debug.__sendvalue = nil
|
||||||
|
|
||||||
|
function debug.get_pack_by_frame(func)
|
||||||
|
local prefix, _ = __parse_path(_debug_getinfo(func, "S").source)
|
||||||
|
return prefix
|
||||||
|
end
|
||||||
|
|
||||||
|
function debug.pull_events()
|
||||||
|
if not is_debugging then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not debug.is_debugging() then
|
||||||
|
is_debugging = false
|
||||||
|
__sethook()
|
||||||
|
end
|
||||||
|
local events = __pull_events()
|
||||||
|
if not events then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
for i, event in ipairs(events) do
|
||||||
|
if event[1] == DBG_EVENT_SET_BREAKPOINT then
|
||||||
|
debug.set_breakpoint(event[2], event[3])
|
||||||
|
elseif event[1] == DBG_EVENT_RM_BREAKPOINT then
|
||||||
|
debug.remove_breakpoint(event[2], event[3])
|
||||||
|
elseif event[1] == DBG_EVENT_STEP then
|
||||||
|
dbg_steps_mode = true
|
||||||
|
dbg_step_into_func = false
|
||||||
|
elseif event[1] == DBG_EVENT_STEP_INTO_FUNCTION then
|
||||||
|
dbg_steps_mode = true
|
||||||
|
dbg_step_into_func = true
|
||||||
|
elseif event[1] == DBG_EVENT_RESUME then
|
||||||
|
dbg_steps_mode = false
|
||||||
|
dbg_step_into_func = false
|
||||||
|
elseif event[1] == DBG_EVENT_GET_VALUE then
|
||||||
|
local _, value = _debug_getlocal(event[2] + 3, event[3])
|
||||||
|
for _, key in ipairs(event[4]) do
|
||||||
|
if value == nil then
|
||||||
|
value = "error: index nil value"
|
||||||
|
break
|
||||||
|
end
|
||||||
|
value = value[key]
|
||||||
|
end
|
||||||
|
__sendvalue(value, event[2], event[3], event[4])
|
||||||
|
__pause()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function debug.set_breakpoint(source, line)
|
||||||
|
local bps = breakpoints[line]
|
||||||
|
if not bps then
|
||||||
|
bps = {}
|
||||||
|
breakpoints[line] = bps
|
||||||
|
end
|
||||||
|
bps[source] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
function debug.remove_breakpoint(source, line)
|
||||||
|
local bps = breakpoints[line]
|
||||||
|
if not bps then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
bps[source] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function error(message, level)
|
||||||
|
if is_debugging then
|
||||||
|
__pause("exception", message)
|
||||||
|
end
|
||||||
|
__error(message, level)
|
||||||
|
end
|
||||||
48
res/modules/internal/deprecated.lua
Normal file
48
res/modules/internal/deprecated.lua
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
-- --------- Deprecated functions ------ --
|
||||||
|
local function wrap_deprecated(func, name, alternatives)
|
||||||
|
return function (...)
|
||||||
|
on_deprecated_call(name, alternatives)
|
||||||
|
return func(...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
block_index = wrap_deprecated(block.index, "block_index", "block.index")
|
||||||
|
block_name = wrap_deprecated(block.name, "block_name", "block.name")
|
||||||
|
blocks_count = wrap_deprecated(block.defs_count, "blocks_count", "block.defs_count")
|
||||||
|
is_solid_at = wrap_deprecated(block.is_solid_at, "is_solid_at", "block.is_solid_at")
|
||||||
|
is_replaceable_at = wrap_deprecated(block.is_replaceable_at, "is_replaceable_at", "block.is_replaceable_at")
|
||||||
|
set_block = wrap_deprecated(block.set, "set_block", "block.set")
|
||||||
|
get_block = wrap_deprecated(block.get, "get_block", "block.get")
|
||||||
|
get_block_X = wrap_deprecated(block.get_X, "get_block_X", "block.get_X")
|
||||||
|
get_block_Y = wrap_deprecated(block.get_Y, "get_block_Y", "block.get_Y")
|
||||||
|
get_block_Z = wrap_deprecated(block.get_Z, "get_block_Z", "block.get_Z")
|
||||||
|
get_block_states = wrap_deprecated(block.get_states, "get_block_states", "block.get_states")
|
||||||
|
set_block_states = wrap_deprecated(block.set_states, "set_block_states", "block.set_states")
|
||||||
|
get_block_rotation = wrap_deprecated(block.get_rotation, "get_block_rotation", "block.get_rotation")
|
||||||
|
set_block_rotation = wrap_deprecated(block.set_rotation, "set_block_rotation", "block.set_rotation")
|
||||||
|
get_block_user_bits = wrap_deprecated(block.get_user_bits, "get_block_user_bits", "block.get_user_bits")
|
||||||
|
set_block_user_bits = wrap_deprecated(block.set_user_bits, "set_block_user_bits", "block.set_user_bits")
|
||||||
|
|
||||||
|
function load_script(path, nocache)
|
||||||
|
on_deprecated_call("load_script", "require or loadstring")
|
||||||
|
return __load_script(path, nocache)
|
||||||
|
end
|
||||||
|
|
||||||
|
_dofile = dofile
|
||||||
|
-- Replaces dofile('*/content/packid/*') with load_script('packid:*')
|
||||||
|
function dofile(path)
|
||||||
|
on_deprecated_call("dofile", "require or loadstring")
|
||||||
|
local index = string.find(path, "/content/")
|
||||||
|
if index then
|
||||||
|
local newpath = string.sub(path, index+9)
|
||||||
|
index = string.find(newpath, "/")
|
||||||
|
if index then
|
||||||
|
local label = string.sub(newpath, 1, index-1)
|
||||||
|
newpath = label..':'..string.sub(newpath, index+1)
|
||||||
|
if file.isfile(newpath) then
|
||||||
|
return __load_script(newpath, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return _dofile(path)
|
||||||
|
end
|
||||||
45
res/modules/internal/extensions/file.lua
Normal file
45
res/modules/internal/extensions/file.lua
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
function file.name(path)
|
||||||
|
return path:match("([^:/\\]+)$")
|
||||||
|
end
|
||||||
|
|
||||||
|
function file.stem(path)
|
||||||
|
local name = file.name(path)
|
||||||
|
return name:match("(.+)%.[^%.]+$") or name
|
||||||
|
end
|
||||||
|
|
||||||
|
function file.ext(path)
|
||||||
|
return path:match("%.([^:/\\]+)$")
|
||||||
|
end
|
||||||
|
|
||||||
|
function file.prefix(path)
|
||||||
|
return path:match("^([^:]+)")
|
||||||
|
end
|
||||||
|
|
||||||
|
function file.parent(path)
|
||||||
|
local dir = path:match("(.*)/")
|
||||||
|
if not dir then
|
||||||
|
return file.prefix(path)..":"
|
||||||
|
end
|
||||||
|
return dir
|
||||||
|
end
|
||||||
|
|
||||||
|
function file.path(path)
|
||||||
|
local pos = path:find(':')
|
||||||
|
return path:sub(pos + 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function file.join(a, b)
|
||||||
|
if a[#a] == ':' then
|
||||||
|
return a .. b
|
||||||
|
end
|
||||||
|
return a .. "/" .. b
|
||||||
|
end
|
||||||
|
|
||||||
|
function file.readlines(path)
|
||||||
|
local str = file.read(path)
|
||||||
|
local lines = {}
|
||||||
|
for s in str:gmatch("[^\r\n]+") do
|
||||||
|
table.insert(lines, s)
|
||||||
|
end
|
||||||
|
return lines
|
||||||
|
end
|
||||||
71
res/modules/internal/extensions/inventory.lua
Normal file
71
res/modules/internal/extensions/inventory.lua
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
function inventory.get_uses(invid, slot)
|
||||||
|
local uses = inventory.get_data(invid, slot, "uses")
|
||||||
|
if uses == nil then
|
||||||
|
return item.uses(inventory.get(invid, slot))
|
||||||
|
end
|
||||||
|
return uses
|
||||||
|
end
|
||||||
|
|
||||||
|
function inventory.use(invid, slot)
|
||||||
|
local itemid, count = inventory.get(invid, slot)
|
||||||
|
if itemid == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local item_uses = inventory.get_uses(invid, slot)
|
||||||
|
if item_uses == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if item_uses == 1 then
|
||||||
|
inventory.set(invid, slot, itemid, count - 1)
|
||||||
|
elseif item_uses > 1 then
|
||||||
|
inventory.set_data(invid, slot, "uses", item_uses - 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function inventory.decrement(invid, slot, count)
|
||||||
|
count = count or 1
|
||||||
|
local itemid, itemcount = inventory.get(invid, slot)
|
||||||
|
if itemcount <= count then
|
||||||
|
inventory.set(invid, slot, 0)
|
||||||
|
else
|
||||||
|
inventory.set_count(invid, slot, itemcount - count)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function inventory.get_caption(invid, slot)
|
||||||
|
local item_id, count = inventory.get(invid, slot)
|
||||||
|
local caption = inventory.get_data(invid, slot, "caption")
|
||||||
|
if not caption then return item.caption(item_id) end
|
||||||
|
|
||||||
|
return caption
|
||||||
|
end
|
||||||
|
|
||||||
|
function inventory.set_caption(invid, slot, caption)
|
||||||
|
local itemid, itemcount = inventory.get(invid, slot)
|
||||||
|
if itemid == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if caption == nil or type(caption) ~= "string" then
|
||||||
|
caption = ""
|
||||||
|
end
|
||||||
|
inventory.set_data(invid, slot, "caption", caption)
|
||||||
|
end
|
||||||
|
|
||||||
|
function inventory.get_description(invid, slot)
|
||||||
|
local item_id, count = inventory.get(invid, slot)
|
||||||
|
local description = inventory.get_data(invid, slot, "description")
|
||||||
|
if not description then return item.description(item_id) end
|
||||||
|
|
||||||
|
return description
|
||||||
|
end
|
||||||
|
|
||||||
|
function inventory.set_description(invid, slot, description)
|
||||||
|
local itemid, itemcount = inventory.get(invid, slot)
|
||||||
|
if itemid == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if description == nil or type(description) ~= "string" then
|
||||||
|
description = ""
|
||||||
|
end
|
||||||
|
inventory.set_data(invid, slot, "description", description)
|
||||||
|
end
|
||||||
37
res/modules/internal/extensions/math.lua
Normal file
37
res/modules/internal/extensions/math.lua
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
function math.clamp(_in, low, high)
|
||||||
|
return math.min(math.max(_in, low), high)
|
||||||
|
end
|
||||||
|
|
||||||
|
function math.rand(low, high)
|
||||||
|
return low + (high - low) * math.random()
|
||||||
|
end
|
||||||
|
|
||||||
|
function math.normalize(num, conf)
|
||||||
|
conf = conf or 1
|
||||||
|
|
||||||
|
return (num / conf) % 1
|
||||||
|
end
|
||||||
|
|
||||||
|
function math.round(num, places)
|
||||||
|
places = places or 0
|
||||||
|
|
||||||
|
local mult = 10 ^ places
|
||||||
|
return math.floor(num * mult + 0.5) / mult
|
||||||
|
end
|
||||||
|
|
||||||
|
function math.sum(...)
|
||||||
|
local numbers = nil
|
||||||
|
local sum = 0
|
||||||
|
|
||||||
|
if type(...) == "table" then
|
||||||
|
numbers = ...
|
||||||
|
else
|
||||||
|
numbers = {...}
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, v in ipairs(numbers) do
|
||||||
|
sum = sum + v
|
||||||
|
end
|
||||||
|
|
||||||
|
return sum
|
||||||
|
end
|
||||||
13
res/modules/internal/extensions/pack.lua
Normal file
13
res/modules/internal/extensions/pack.lua
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
function pack.is_installed(packid)
|
||||||
|
return file.isfile(packid..":package.json")
|
||||||
|
end
|
||||||
|
|
||||||
|
function pack.data_file(packid, name)
|
||||||
|
file.mkdirs("world:data/"..packid)
|
||||||
|
return "world:data/"..packid.."/"..name
|
||||||
|
end
|
||||||
|
|
||||||
|
function pack.shared_file(packid, name)
|
||||||
|
file.mkdirs("config:"..packid)
|
||||||
|
return "config:"..packid.."/"..name
|
||||||
|
end
|
||||||
126
res/modules/internal/extensions/string.lua
Normal file
126
res/modules/internal/extensions/string.lua
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
local pattern_escape_replacements = {
|
||||||
|
["("] = "%(",
|
||||||
|
[")"] = "%)",
|
||||||
|
["."] = "%.",
|
||||||
|
["%"] = "%%",
|
||||||
|
["+"] = "%+",
|
||||||
|
["-"] = "%-",
|
||||||
|
["*"] = "%*",
|
||||||
|
["?"] = "%?",
|
||||||
|
["["] = "%[",
|
||||||
|
["]"] = "%]",
|
||||||
|
["^"] = "%^",
|
||||||
|
["$"] = "%$",
|
||||||
|
["\0"] = "%z"
|
||||||
|
}
|
||||||
|
|
||||||
|
function string.pattern_safe(str)
|
||||||
|
return string.gsub(str, ".", pattern_escape_replacements)
|
||||||
|
end
|
||||||
|
|
||||||
|
local string_sub = string.sub
|
||||||
|
local string_find = string.find
|
||||||
|
local string_len = string.len
|
||||||
|
function string.explode(separator, str, withpattern)
|
||||||
|
if (withpattern == nil) then withpattern = false end
|
||||||
|
|
||||||
|
local ret = {}
|
||||||
|
local current_pos = 1
|
||||||
|
|
||||||
|
for i = 1, string_len(str) do
|
||||||
|
local start_pos, end_pos = string_find(
|
||||||
|
str, separator, current_pos, not withpattern)
|
||||||
|
if (not start_pos) then break end
|
||||||
|
ret[i] = string_sub(str, current_pos, start_pos - 1)
|
||||||
|
current_pos = end_pos + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
ret[#ret + 1] = string_sub(str, current_pos)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.split(str, delimiter)
|
||||||
|
return string.explode(delimiter, str)
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.formatted_time(seconds, format)
|
||||||
|
if (not seconds) then seconds = 0 end
|
||||||
|
local hours = math.floor(seconds / 3600)
|
||||||
|
local minutes = math.floor((seconds / 60) % 60)
|
||||||
|
local millisecs = (seconds - math.floor(seconds)) * 1000
|
||||||
|
seconds = math.floor(seconds % 60)
|
||||||
|
|
||||||
|
if (format) then
|
||||||
|
return string.format(format, minutes, seconds, millisecs)
|
||||||
|
else
|
||||||
|
return { h = hours, m = minutes, s = seconds, ms = millisecs }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.replace(str, tofind, toreplace)
|
||||||
|
local tbl = string.explode(tofind, str)
|
||||||
|
if (tbl[1]) then return table.concat(tbl, toreplace) end
|
||||||
|
return str
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.trim(s, char)
|
||||||
|
if char then char = string.pattern_safe(char) else char = "%s" end
|
||||||
|
return string.match(s, "^" .. char .. "*(.-)" .. char .. "*$") or s
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.trim_right(s, char)
|
||||||
|
if char then char = string.pattern_safe(char) else char = "%s" end
|
||||||
|
return string.match(s, "^(.-)" .. char .. "*$") or s
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.trim_left(s, char)
|
||||||
|
if char then char = string.pattern_safe(char) else char = "%s" end
|
||||||
|
return string.match(s, "^" .. char .. "*(.+)$") or s
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.pad(str, size, char)
|
||||||
|
char = char == nil and " " or char
|
||||||
|
|
||||||
|
local padding = math.floor((size - #str) / 2)
|
||||||
|
local extra_padding = (size - #str) % 2
|
||||||
|
|
||||||
|
return string.rep(char, padding) .. str .. string.rep(char, padding + extra_padding)
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.left_pad(str, size, char)
|
||||||
|
char = char == nil and " " or char
|
||||||
|
|
||||||
|
local left_padding = size - #str
|
||||||
|
return string.rep(char, left_padding) .. str
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.right_pad(str, size, char)
|
||||||
|
char = char == nil and " " or char
|
||||||
|
|
||||||
|
local right_padding = size - #str
|
||||||
|
return str .. string.rep(char, right_padding)
|
||||||
|
end
|
||||||
|
|
||||||
|
string.lower = utf8.lower
|
||||||
|
string.upper = utf8.upper
|
||||||
|
string.escape = utf8.escape
|
||||||
|
|
||||||
|
local meta = getmetatable("")
|
||||||
|
|
||||||
|
function meta:__index(key)
|
||||||
|
local val = string[key]
|
||||||
|
if (val ~= nil) then
|
||||||
|
return val
|
||||||
|
elseif (tonumber(key)) then
|
||||||
|
return string.sub(self, key, key)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.starts_with(str, start)
|
||||||
|
return string.sub(str, 1, string.len(start)) == start
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.ends_with(str, endStr)
|
||||||
|
return endStr == "" or string.sub(str, -string.len(endStr)) == endStr
|
||||||
|
end
|
||||||
179
res/modules/internal/extensions/table.lua
Normal file
179
res/modules/internal/extensions/table.lua
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
function table.copy(t)
|
||||||
|
local copied = {}
|
||||||
|
|
||||||
|
for k, v in pairs(t) do
|
||||||
|
copied[k] = v
|
||||||
|
end
|
||||||
|
|
||||||
|
return copied
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.deep_copy(t)
|
||||||
|
local copied = {}
|
||||||
|
|
||||||
|
for k, v in pairs(t) do
|
||||||
|
if type(v) == "table" then
|
||||||
|
copied[k] = table.deep_copy(v)
|
||||||
|
else
|
||||||
|
copied[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return setmetatable(copied, getmetatable(t))
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.count_pairs(t)
|
||||||
|
local count = 0
|
||||||
|
|
||||||
|
for k, v in pairs(t) do
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.random(t)
|
||||||
|
return t[math.random(1, #t)]
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.shuffle(t)
|
||||||
|
for i = #t, 2, -1 do
|
||||||
|
local j = math.random(i)
|
||||||
|
t[i], t[j] = t[j], t[i]
|
||||||
|
end
|
||||||
|
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.merge(t1, t2)
|
||||||
|
for i, v in pairs(t2) do
|
||||||
|
if type(i) == "number" then
|
||||||
|
t1[#t1 + 1] = v
|
||||||
|
elseif t1[i] == nil then
|
||||||
|
t1[i] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return t1
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.map(t, func)
|
||||||
|
for i, v in pairs(t) do
|
||||||
|
t[i] = func(i, v)
|
||||||
|
end
|
||||||
|
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.filter(t, func)
|
||||||
|
|
||||||
|
for i = #t, 1, -1 do
|
||||||
|
if not func(i, t[i]) then
|
||||||
|
table.remove(t, i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local size = #t
|
||||||
|
|
||||||
|
for i, v in pairs(t) do
|
||||||
|
local i_type = type(i)
|
||||||
|
if i_type == "number" then
|
||||||
|
if i < 1 or i > size then
|
||||||
|
if not func(i, v) then
|
||||||
|
t[i] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if not func(i, v) then
|
||||||
|
t[i] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.set_default(t, key, default)
|
||||||
|
if t[key] == nil then
|
||||||
|
t[key] = default
|
||||||
|
return default
|
||||||
|
end
|
||||||
|
|
||||||
|
return t[key]
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.flat(t)
|
||||||
|
local flat = {}
|
||||||
|
|
||||||
|
for _, v in pairs(t) do
|
||||||
|
if type(v) == "table" then
|
||||||
|
table.merge(flat, v)
|
||||||
|
else
|
||||||
|
table.insert(flat, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return flat
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.deep_flat(t)
|
||||||
|
local flat = {}
|
||||||
|
|
||||||
|
for _, v in pairs(t) do
|
||||||
|
if type(v) == "table" then
|
||||||
|
table.merge(flat, table.deep_flat(v))
|
||||||
|
else
|
||||||
|
table.insert(flat, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return flat
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.sub(arr, start, stop)
|
||||||
|
local res = {}
|
||||||
|
start = start or 1
|
||||||
|
stop = stop or #arr
|
||||||
|
|
||||||
|
for i = start, stop do
|
||||||
|
table.insert(res, arr[i])
|
||||||
|
end
|
||||||
|
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.has(t, x)
|
||||||
|
for i,v in ipairs(t) do
|
||||||
|
if v == x then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.index(t, x)
|
||||||
|
for i,v in ipairs(t) do
|
||||||
|
if v == x then
|
||||||
|
return i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return -1
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.remove_value(t, x)
|
||||||
|
local index = table.index(t, x)
|
||||||
|
if index ~= -1 then
|
||||||
|
table.remove(t, index)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.tostring(t)
|
||||||
|
local s = '['
|
||||||
|
for i,v in ipairs(t) do
|
||||||
|
s = s..tostring(v)
|
||||||
|
if i < #t then
|
||||||
|
s = s..', '
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return s..']'
|
||||||
|
end
|
||||||
70
res/modules/internal/rules.lua
Normal file
70
res/modules/internal/rules.lua
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
local rules = {nexid = 1, rules = {}}
|
||||||
|
|
||||||
|
function rules.get_rule(name)
|
||||||
|
local rule = rules.rules[name]
|
||||||
|
if rule == nil then
|
||||||
|
rule = {listeners={}}
|
||||||
|
rules.rules[name] = rule
|
||||||
|
end
|
||||||
|
return rule
|
||||||
|
end
|
||||||
|
|
||||||
|
function rules.get(name)
|
||||||
|
local rule = rules.rules[name]
|
||||||
|
if rule == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return rule.value
|
||||||
|
end
|
||||||
|
|
||||||
|
function rules.set(name, value)
|
||||||
|
local rule = rules.get_rule(name)
|
||||||
|
rule.value = value
|
||||||
|
for _, handler in pairs(rule.listeners) do
|
||||||
|
handler(value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function rules.reset(name)
|
||||||
|
local rule = rules.get_rule(name)
|
||||||
|
rules.set(rule.default)
|
||||||
|
end
|
||||||
|
|
||||||
|
function rules.listen(name, handler)
|
||||||
|
local rule = rules.get_rule(name)
|
||||||
|
local id = rules.nexid
|
||||||
|
rules.nextid = rules.nexid + 1
|
||||||
|
rule.listeners[utf8.encode(id)] = handler
|
||||||
|
return id
|
||||||
|
end
|
||||||
|
|
||||||
|
function rules.create(name, value, handler)
|
||||||
|
local rule = rules.get_rule(name)
|
||||||
|
rule.default = value
|
||||||
|
|
||||||
|
local handlerid
|
||||||
|
if handler ~= nil then
|
||||||
|
handlerid = rules.listen(name, handler)
|
||||||
|
end
|
||||||
|
if rules.get(name) == nil then
|
||||||
|
rules.set(name, value)
|
||||||
|
elseif handler then
|
||||||
|
handler(rules.get(name))
|
||||||
|
end
|
||||||
|
return handlerid
|
||||||
|
end
|
||||||
|
|
||||||
|
function rules.unlisten(name, id)
|
||||||
|
local rule = rules.rules[name]
|
||||||
|
if rule == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
rule.listeners[utf8.encode(id)] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function rules.clear()
|
||||||
|
rules.rules = {}
|
||||||
|
rules.nextid = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return rules
|
||||||
@ -106,82 +106,10 @@ elseif __vc_app then
|
|||||||
complete_app_lib(__vc_app)
|
complete_app_lib(__vc_app)
|
||||||
end
|
end
|
||||||
|
|
||||||
function inventory.get_uses(invid, slot)
|
require "core:internal/maths_inline"
|
||||||
local uses = inventory.get_data(invid, slot, "uses")
|
require "core:internal/debugging"
|
||||||
if uses == nil then
|
require "core:internal/audio_input"
|
||||||
return item.uses(inventory.get(invid, slot))
|
require "core:internal/extensions/inventory"
|
||||||
end
|
|
||||||
return uses
|
|
||||||
end
|
|
||||||
|
|
||||||
function inventory.use(invid, slot)
|
|
||||||
local itemid, count = inventory.get(invid, slot)
|
|
||||||
if itemid == nil then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local item_uses = inventory.get_uses(invid, slot)
|
|
||||||
if item_uses == nil then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if item_uses == 1 then
|
|
||||||
inventory.set(invid, slot, itemid, count - 1)
|
|
||||||
elseif item_uses > 1 then
|
|
||||||
inventory.set_data(invid, slot, "uses", item_uses - 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function inventory.decrement(invid, slot, count)
|
|
||||||
count = count or 1
|
|
||||||
local itemid, itemcount = inventory.get(invid, slot)
|
|
||||||
if itemcount <= count then
|
|
||||||
inventory.set(invid, slot, 0)
|
|
||||||
else
|
|
||||||
inventory.set_count(invid, slot, itemcount - count)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function inventory.get_caption(invid, slot)
|
|
||||||
local item_id, count = inventory.get(invid, slot)
|
|
||||||
local caption = inventory.get_data(invid, slot, "caption")
|
|
||||||
if not caption then return item.caption(item_id) end
|
|
||||||
|
|
||||||
return caption
|
|
||||||
end
|
|
||||||
|
|
||||||
function inventory.set_caption(invid, slot, caption)
|
|
||||||
local itemid, itemcount = inventory.get(invid, slot)
|
|
||||||
if itemid == 0 then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if caption == nil or type(caption) ~= "string" then
|
|
||||||
caption = ""
|
|
||||||
end
|
|
||||||
inventory.set_data(invid, slot, "caption", caption)
|
|
||||||
end
|
|
||||||
|
|
||||||
function inventory.get_description(invid, slot)
|
|
||||||
local item_id, count = inventory.get(invid, slot)
|
|
||||||
local description = inventory.get_data(invid, slot, "description")
|
|
||||||
if not description then return item.description(item_id) end
|
|
||||||
|
|
||||||
return description
|
|
||||||
end
|
|
||||||
|
|
||||||
function inventory.set_description(invid, slot, description)
|
|
||||||
local itemid, itemcount = inventory.get(invid, slot)
|
|
||||||
if itemid == 0 then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if description == nil or type(description) ~= "string" then
|
|
||||||
description = ""
|
|
||||||
end
|
|
||||||
inventory.set_data(invid, slot, "description", description)
|
|
||||||
end
|
|
||||||
|
|
||||||
if enable_experimental then
|
|
||||||
require "core:internal/maths_inline"
|
|
||||||
end
|
|
||||||
|
|
||||||
asserts = require "core:internal/asserts"
|
asserts = require "core:internal/asserts"
|
||||||
events = require "core:internal/events"
|
events = require "core:internal/events"
|
||||||
|
|
||||||
@ -297,11 +225,6 @@ entities.get_all = function(uids)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local bytearray = require "core:internal/bytearray"
|
|
||||||
Bytearray = bytearray.FFIBytearray
|
|
||||||
Bytearray_as_string = bytearray.FFIBytearray_as_string
|
|
||||||
Bytearray_construct = function(...) return Bytearray(...) end
|
|
||||||
|
|
||||||
__vc_scripts_registry = require "core:internal/scripts_registry"
|
__vc_scripts_registry = require "core:internal/scripts_registry"
|
||||||
|
|
||||||
file.open = require "core:internal/stream_providers/file"
|
file.open = require "core:internal/stream_providers/file"
|
||||||
@ -321,86 +244,20 @@ else
|
|||||||
os.pid = ffi.C.getpid()
|
os.pid = ffi.C.getpid()
|
||||||
end
|
end
|
||||||
|
|
||||||
ffi = nil
|
|
||||||
__vc_lock_internal_modules()
|
|
||||||
|
|
||||||
math.randomseed(time.uptime() * 1536227939)
|
math.randomseed(time.uptime() * 1536227939)
|
||||||
|
|
||||||
rules = {nexid = 1, rules = {}}
|
rules = require "core:internal/rules"
|
||||||
local _rules = rules
|
local _rules = rules
|
||||||
|
|
||||||
function _rules.get_rule(name)
|
|
||||||
local rule = _rules.rules[name]
|
|
||||||
if rule == nil then
|
|
||||||
rule = {listeners={}}
|
|
||||||
_rules.rules[name] = rule
|
|
||||||
end
|
|
||||||
return rule
|
|
||||||
end
|
|
||||||
|
|
||||||
function _rules.get(name)
|
|
||||||
local rule = _rules.rules[name]
|
|
||||||
if rule == nil then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
return rule.value
|
|
||||||
end
|
|
||||||
|
|
||||||
function _rules.set(name, value)
|
|
||||||
local rule = _rules.get_rule(name)
|
|
||||||
rule.value = value
|
|
||||||
for _, handler in pairs(rule.listeners) do
|
|
||||||
handler(value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function _rules.reset(name)
|
|
||||||
local rule = _rules.get_rule(name)
|
|
||||||
_rules.set(rule.default)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _rules.listen(name, handler)
|
|
||||||
local rule = _rules.get_rule(name)
|
|
||||||
local id = _rules.nexid
|
|
||||||
_rules.nextid = _rules.nexid + 1
|
|
||||||
rule.listeners[utf8.encode(id)] = handler
|
|
||||||
return id
|
|
||||||
end
|
|
||||||
|
|
||||||
function _rules.create(name, value, handler)
|
|
||||||
local rule = _rules.get_rule(name)
|
|
||||||
rule.default = value
|
|
||||||
|
|
||||||
local handlerid
|
|
||||||
if handler ~= nil then
|
|
||||||
handlerid = _rules.listen(name, handler)
|
|
||||||
end
|
|
||||||
if _rules.get(name) == nil then
|
|
||||||
_rules.set(name, value)
|
|
||||||
elseif handler then
|
|
||||||
handler(_rules.get(name))
|
|
||||||
end
|
|
||||||
return handlerid
|
|
||||||
end
|
|
||||||
|
|
||||||
function _rules.unlisten(name, id)
|
|
||||||
local rule = _rules.rules[name]
|
|
||||||
if rule == nil then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
rule.listeners[utf8.encode(id)] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function _rules.clear()
|
|
||||||
_rules.rules = {}
|
|
||||||
_rules.nextid = 1
|
|
||||||
end
|
|
||||||
|
|
||||||
function __vc_on_hud_open()
|
function __vc_on_hud_open()
|
||||||
|
local _hud_is_content_access = hud._is_content_access
|
||||||
|
local _hud_set_content_access = hud._set_content_access
|
||||||
|
local _hud_set_debug_cheats = hud._set_debug_cheats
|
||||||
|
|
||||||
_rules.create("allow-cheats", true)
|
_rules.create("allow-cheats", true)
|
||||||
|
|
||||||
_rules.create("allow-content-access", hud._is_content_access(), function(value)
|
_rules.create("allow-content-access", _hud_is_content_access(), function(value)
|
||||||
hud._set_content_access(value)
|
_hud_set_content_access(value)
|
||||||
end)
|
end)
|
||||||
_rules.create("allow-flight", true, function(value)
|
_rules.create("allow-flight", true, function(value)
|
||||||
input.set_enabled("player.flight", value)
|
input.set_enabled("player.flight", value)
|
||||||
@ -421,7 +278,7 @@ function __vc_on_hud_open()
|
|||||||
input.set_enabled("player.fast_interaction", value)
|
input.set_enabled("player.fast_interaction", value)
|
||||||
end)
|
end)
|
||||||
_rules.create("allow-debug-cheats", true, function(value)
|
_rules.create("allow-debug-cheats", true, function(value)
|
||||||
hud._set_debug_cheats(value)
|
_hud_set_debug_cheats(value)
|
||||||
end)
|
end)
|
||||||
input.add_callback("devtools.console", function()
|
input.add_callback("devtools.console", function()
|
||||||
if menu.page ~= "" then
|
if menu.page ~= "" then
|
||||||
@ -578,6 +435,9 @@ end
|
|||||||
|
|
||||||
local __post_runnables = {}
|
local __post_runnables = {}
|
||||||
|
|
||||||
|
local fn_audio_reset_fetch_buffer = audio.__reset_fetch_buffer
|
||||||
|
audio.__reset_fetch_buffer = nil
|
||||||
|
|
||||||
function __process_post_runnables()
|
function __process_post_runnables()
|
||||||
if #__post_runnables then
|
if #__post_runnables then
|
||||||
for _, func in ipairs(__post_runnables) do
|
for _, func in ipairs(__post_runnables) do
|
||||||
@ -603,6 +463,7 @@ function __process_post_runnables()
|
|||||||
__vc_named_coroutines[name] = nil
|
__vc_named_coroutines[name] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
fn_audio_reset_fetch_buffer()
|
||||||
debug.pull_events()
|
debug.pull_events()
|
||||||
network.__process_events()
|
network.__process_events()
|
||||||
block.__process_register_events()
|
block.__process_register_events()
|
||||||
@ -622,6 +483,7 @@ local _getinfo = debug.getinfo
|
|||||||
for i, name in ipairs(removed_names) do
|
for i, name in ipairs(removed_names) do
|
||||||
debug[name] = nil
|
debug[name] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
debug.getinfo = function(lvl, fields)
|
debug.getinfo = function(lvl, fields)
|
||||||
if type(lvl) == "number" then
|
if type(lvl) == "number" then
|
||||||
lvl = lvl + 1
|
lvl = lvl + 1
|
||||||
@ -631,51 +493,7 @@ debug.getinfo = function(lvl, fields)
|
|||||||
return debuginfo
|
return debuginfo
|
||||||
end
|
end
|
||||||
|
|
||||||
-- --------- Deprecated functions ------ --
|
require "core:internal/deprecated"
|
||||||
local function wrap_deprecated(func, name, alternatives)
|
|
||||||
return function (...)
|
|
||||||
on_deprecated_call(name, alternatives)
|
|
||||||
return func(...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
block_index = wrap_deprecated(block.index, "block_index", "block.index")
|
ffi = nil
|
||||||
block_name = wrap_deprecated(block.name, "block_name", "block.name")
|
__vc_lock_internal_modules()
|
||||||
blocks_count = wrap_deprecated(block.defs_count, "blocks_count", "block.defs_count")
|
|
||||||
is_solid_at = wrap_deprecated(block.is_solid_at, "is_solid_at", "block.is_solid_at")
|
|
||||||
is_replaceable_at = wrap_deprecated(block.is_replaceable_at, "is_replaceable_at", "block.is_replaceable_at")
|
|
||||||
set_block = wrap_deprecated(block.set, "set_block", "block.set")
|
|
||||||
get_block = wrap_deprecated(block.get, "get_block", "block.get")
|
|
||||||
get_block_X = wrap_deprecated(block.get_X, "get_block_X", "block.get_X")
|
|
||||||
get_block_Y = wrap_deprecated(block.get_Y, "get_block_Y", "block.get_Y")
|
|
||||||
get_block_Z = wrap_deprecated(block.get_Z, "get_block_Z", "block.get_Z")
|
|
||||||
get_block_states = wrap_deprecated(block.get_states, "get_block_states", "block.get_states")
|
|
||||||
set_block_states = wrap_deprecated(block.set_states, "set_block_states", "block.set_states")
|
|
||||||
get_block_rotation = wrap_deprecated(block.get_rotation, "get_block_rotation", "block.get_rotation")
|
|
||||||
set_block_rotation = wrap_deprecated(block.set_rotation, "set_block_rotation", "block.set_rotation")
|
|
||||||
get_block_user_bits = wrap_deprecated(block.get_user_bits, "get_block_user_bits", "block.get_user_bits")
|
|
||||||
set_block_user_bits = wrap_deprecated(block.set_user_bits, "set_block_user_bits", "block.set_user_bits")
|
|
||||||
|
|
||||||
function load_script(path, nocache)
|
|
||||||
on_deprecated_call("load_script", "require or loadstring")
|
|
||||||
return __load_script(path, nocache)
|
|
||||||
end
|
|
||||||
|
|
||||||
_dofile = dofile
|
|
||||||
-- Replaces dofile('*/content/packid/*') with load_script('packid:*')
|
|
||||||
function dofile(path)
|
|
||||||
on_deprecated_call("dofile", "require or loadstring")
|
|
||||||
local index = string.find(path, "/content/")
|
|
||||||
if index then
|
|
||||||
local newpath = string.sub(path, index+9)
|
|
||||||
index = string.find(newpath, "/")
|
|
||||||
if index then
|
|
||||||
local label = string.sub(newpath, 1, index-1)
|
|
||||||
newpath = label..':'..string.sub(newpath, index+1)
|
|
||||||
if file.isfile(newpath) then
|
|
||||||
return __load_script(newpath, true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return _dofile(path)
|
|
||||||
end
|
|
||||||
|
|||||||
@ -1,201 +1,8 @@
|
|||||||
local breakpoints = {}
|
|
||||||
local dbg_steps_mode = false
|
|
||||||
local dbg_step_into_func = false
|
|
||||||
local hook_lock = false
|
|
||||||
local current_func
|
|
||||||
local current_func_stack_size
|
|
||||||
|
|
||||||
local _debug_getinfo = debug.getinfo
|
|
||||||
local _debug_getlocal = debug.getlocal
|
|
||||||
local __pause = debug.pause
|
|
||||||
local __error = error
|
|
||||||
local __sethook = debug.sethook
|
|
||||||
|
|
||||||
-- 'return' hook not called for some functions
|
|
||||||
-- todo: speedup
|
|
||||||
local function calc_stack_size()
|
|
||||||
local s = debug.traceback("", 2)
|
|
||||||
local count = 0
|
|
||||||
for i in s:gmatch("\n") do
|
|
||||||
count = count + 1
|
|
||||||
end
|
|
||||||
return count
|
|
||||||
end
|
|
||||||
|
|
||||||
local is_debugging = debug.is_debugging()
|
|
||||||
if is_debugging then
|
|
||||||
__sethook(function (e, line)
|
|
||||||
if e == "return" then
|
|
||||||
local info = _debug_getinfo(2)
|
|
||||||
if info.func == current_func then
|
|
||||||
current_func = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if dbg_steps_mode and not hook_lock then
|
|
||||||
hook_lock = true
|
|
||||||
|
|
||||||
if not dbg_step_into_func then
|
|
||||||
local func = _debug_getinfo(2).func
|
|
||||||
if func ~= current_func then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if current_func_stack_size ~= calc_stack_size() then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
current_func = func
|
|
||||||
__pause("step")
|
|
||||||
debug.pull_events()
|
|
||||||
end
|
|
||||||
hook_lock = false
|
|
||||||
local bps = breakpoints[line]
|
|
||||||
if not bps then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local source = _debug_getinfo(2).source
|
|
||||||
if not bps[source] then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
current_func = _debug_getinfo(2).func
|
|
||||||
current_func_stack_size = calc_stack_size()
|
|
||||||
__pause("breakpoint")
|
|
||||||
debug.pull_events()
|
|
||||||
end, "lr")
|
|
||||||
end
|
|
||||||
|
|
||||||
local DBG_EVENT_SET_BREAKPOINT = 1
|
|
||||||
local DBG_EVENT_RM_BREAKPOINT = 2
|
|
||||||
local DBG_EVENT_STEP = 3
|
|
||||||
local DBG_EVENT_STEP_INTO_FUNCTION = 4
|
|
||||||
local DBG_EVENT_RESUME = 5
|
|
||||||
local DBG_EVENT_GET_VALUE = 6
|
|
||||||
local __pull_events = debug.__pull_events
|
|
||||||
local __sendvalue = debug.__sendvalue
|
|
||||||
debug.__pull_events = nil
|
|
||||||
debug.__sendvalue = nil
|
|
||||||
|
|
||||||
function debug.pull_events()
|
|
||||||
if not is_debugging then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if not debug.is_debugging() then
|
|
||||||
is_debugging = false
|
|
||||||
__sethook()
|
|
||||||
end
|
|
||||||
local events = __pull_events()
|
|
||||||
if not events then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
for i, event in ipairs(events) do
|
|
||||||
if event[1] == DBG_EVENT_SET_BREAKPOINT then
|
|
||||||
debug.set_breakpoint(event[2], event[3])
|
|
||||||
elseif event[1] == DBG_EVENT_RM_BREAKPOINT then
|
|
||||||
debug.remove_breakpoint(event[2], event[3])
|
|
||||||
elseif event[1] == DBG_EVENT_STEP then
|
|
||||||
dbg_steps_mode = true
|
|
||||||
dbg_step_into_func = false
|
|
||||||
elseif event[1] == DBG_EVENT_STEP_INTO_FUNCTION then
|
|
||||||
dbg_steps_mode = true
|
|
||||||
dbg_step_into_func = true
|
|
||||||
elseif event[1] == DBG_EVENT_RESUME then
|
|
||||||
dbg_steps_mode = false
|
|
||||||
dbg_step_into_func = false
|
|
||||||
elseif event[1] == DBG_EVENT_GET_VALUE then
|
|
||||||
local _, value = _debug_getlocal(event[2] + 3, event[3])
|
|
||||||
for _, key in ipairs(event[4]) do
|
|
||||||
if value == nil then
|
|
||||||
value = "error: index nil value"
|
|
||||||
break
|
|
||||||
end
|
|
||||||
value = value[key]
|
|
||||||
end
|
|
||||||
__sendvalue(value, event[2], event[3], event[4])
|
|
||||||
__pause()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function debug.set_breakpoint(source, line)
|
|
||||||
local bps = breakpoints[line]
|
|
||||||
if not bps then
|
|
||||||
bps = {}
|
|
||||||
breakpoints[line] = bps
|
|
||||||
end
|
|
||||||
bps[source] = true
|
|
||||||
end
|
|
||||||
|
|
||||||
function debug.remove_breakpoint(source, line)
|
|
||||||
local bps = breakpoints[line]
|
|
||||||
if not bps then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
bps[source] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function error(message, level)
|
|
||||||
if is_debugging then
|
|
||||||
__pause("exception", message)
|
|
||||||
end
|
|
||||||
__error(message, level)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Lua has no parallelizm, also _set_data does not call any lua functions so
|
|
||||||
-- may be reused one global ffi buffer per lua_State
|
|
||||||
local canvas_ffi_buffer
|
|
||||||
local canvas_ffi_buffer_size = 0
|
|
||||||
|
|
||||||
local ipairs_mt_supported = false
|
|
||||||
for i, _ in ipairs(setmetatable({l={1}}, {
|
|
||||||
__ipairs=function(self) return ipairs(self.l) end})) do
|
|
||||||
ipairs_mt_supported = true
|
|
||||||
end
|
|
||||||
|
|
||||||
if not ipairs_mt_supported then
|
|
||||||
local raw_ipairs = ipairs
|
|
||||||
ipairs = function(t)
|
|
||||||
local metatable = getmetatable(t)
|
|
||||||
if metatable and metatable.__ipairs then
|
|
||||||
return metatable.__ipairs(t)
|
|
||||||
end
|
|
||||||
return raw_ipairs(t)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function await(co)
|
|
||||||
local res, err
|
|
||||||
while coroutine.status(co) ~= 'dead' do
|
|
||||||
coroutine.yield()
|
|
||||||
res, err = coroutine.resume(co)
|
|
||||||
if err then
|
|
||||||
return res, err
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return res, err
|
|
||||||
end
|
|
||||||
|
|
||||||
local _ffi = ffi
|
local _ffi = ffi
|
||||||
function __vc_Canvas_set_data(self, data)
|
local _debug_getinfo = debug.getinfo
|
||||||
if type(data) == "cdata" then
|
|
||||||
self:_set_data(tostring(_ffi.cast("uintptr_t", data)))
|
|
||||||
end
|
|
||||||
local width = self.width
|
|
||||||
local height = self.height
|
|
||||||
|
|
||||||
local size = width * height * 4
|
|
||||||
if size > canvas_ffi_buffer_size then
|
|
||||||
canvas_ffi_buffer = _ffi.new(
|
|
||||||
string.format("unsigned char[%s]", size)
|
|
||||||
)
|
|
||||||
canvas_ffi_buffer_size = size
|
|
||||||
end
|
|
||||||
for i=0, size - 1 do
|
|
||||||
canvas_ffi_buffer[i] = data[i + 1]
|
|
||||||
end
|
|
||||||
self:_set_data(tostring(_ffi.cast("uintptr_t", canvas_ffi_buffer)))
|
|
||||||
end
|
|
||||||
|
|
||||||
function crc32(bytes, chksum)
|
function crc32(bytes, chksum)
|
||||||
local chksum = chksum or 0
|
chksum = chksum or 0
|
||||||
|
|
||||||
local length = #bytes
|
local length = #bytes
|
||||||
if type(bytes) == "table" then
|
if type(bytes) == "table" then
|
||||||
@ -231,20 +38,59 @@ function parse_path(path)
|
|||||||
return string.sub(path, 1, index-1), string.sub(path, index+1, -1)
|
return string.sub(path, 1, index-1), string.sub(path, index+1, -1)
|
||||||
end
|
end
|
||||||
|
|
||||||
function pack.is_installed(packid)
|
-- Lua has no parallelizm, also _set_data does not call any lua functions so
|
||||||
return file.isfile(packid..":package.json")
|
-- may be reused one global ffi buffer per lua_State
|
||||||
|
local canvas_ffi_buffer
|
||||||
|
local canvas_ffi_buffer_size = 0
|
||||||
|
local _ffi = ffi
|
||||||
|
function __vc_Canvas_set_data(self, data)
|
||||||
|
if type(data) == "cdata" then
|
||||||
|
self:_set_data(tostring(_ffi.cast("uintptr_t", data)))
|
||||||
|
end
|
||||||
|
local width = self.width
|
||||||
|
local height = self.height
|
||||||
|
|
||||||
|
local size = width * height * 4
|
||||||
|
if size > canvas_ffi_buffer_size then
|
||||||
|
canvas_ffi_buffer = _ffi.new(
|
||||||
|
string.format("unsigned char[%s]", size)
|
||||||
|
)
|
||||||
|
canvas_ffi_buffer_size = size
|
||||||
|
end
|
||||||
|
for i=0, size - 1 do
|
||||||
|
canvas_ffi_buffer[i] = data[i + 1]
|
||||||
|
end
|
||||||
|
self:_set_data(tostring(_ffi.cast("uintptr_t", canvas_ffi_buffer)))
|
||||||
end
|
end
|
||||||
|
|
||||||
function pack.data_file(packid, name)
|
local ipairs_mt_supported = false
|
||||||
file.mkdirs("world:data/"..packid)
|
for i, _ in ipairs(setmetatable({l={1}}, {
|
||||||
return "world:data/"..packid.."/"..name
|
__ipairs=function(self) return ipairs(self.l) end})) do
|
||||||
|
ipairs_mt_supported = true
|
||||||
end
|
end
|
||||||
|
|
||||||
function pack.shared_file(packid, name)
|
if not ipairs_mt_supported then
|
||||||
file.mkdirs("config:"..packid)
|
local raw_ipairs = ipairs
|
||||||
return "config:"..packid.."/"..name
|
ipairs = function(t)
|
||||||
|
local metatable = getmetatable(t)
|
||||||
|
if metatable and metatable.__ipairs then
|
||||||
|
return metatable.__ipairs(t)
|
||||||
|
end
|
||||||
|
return raw_ipairs(t)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function await(co)
|
||||||
|
local res, err
|
||||||
|
while coroutine.status(co) ~= 'dead' do
|
||||||
|
coroutine.yield()
|
||||||
|
res, err = coroutine.resume(co)
|
||||||
|
if err then
|
||||||
|
return res, err
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return res, err
|
||||||
|
end
|
||||||
|
|
||||||
function timeit(iters, func, ...)
|
function timeit(iters, func, ...)
|
||||||
local tm = os.clock()
|
local tm = os.clock()
|
||||||
@ -256,364 +102,6 @@ end
|
|||||||
|
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
|
|
||||||
function math.clamp(_in, low, high)
|
|
||||||
return math.min(math.max(_in, low), high)
|
|
||||||
end
|
|
||||||
|
|
||||||
function math.rand(low, high)
|
|
||||||
return low + (high - low) * math.random()
|
|
||||||
end
|
|
||||||
|
|
||||||
function math.normalize(num, conf)
|
|
||||||
conf = conf or 1
|
|
||||||
|
|
||||||
return (num / conf) % 1
|
|
||||||
end
|
|
||||||
|
|
||||||
function math.round(num, places)
|
|
||||||
places = places or 0
|
|
||||||
|
|
||||||
local mult = 10 ^ places
|
|
||||||
return math.floor(num * mult + 0.5) / mult
|
|
||||||
end
|
|
||||||
|
|
||||||
function math.sum(...)
|
|
||||||
local numbers = nil
|
|
||||||
local sum = 0
|
|
||||||
|
|
||||||
if type(...) == "table" then
|
|
||||||
numbers = ...
|
|
||||||
else
|
|
||||||
numbers = {...}
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, v in ipairs(numbers) do
|
|
||||||
sum = sum + v
|
|
||||||
end
|
|
||||||
|
|
||||||
return sum
|
|
||||||
end
|
|
||||||
|
|
||||||
----------------------------------------------
|
|
||||||
|
|
||||||
function table.copy(t)
|
|
||||||
local copied = {}
|
|
||||||
|
|
||||||
for k, v in pairs(t) do
|
|
||||||
copied[k] = v
|
|
||||||
end
|
|
||||||
|
|
||||||
return copied
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.deep_copy(t)
|
|
||||||
local copied = {}
|
|
||||||
|
|
||||||
for k, v in pairs(t) do
|
|
||||||
if type(v) == "table" then
|
|
||||||
copied[k] = table.deep_copy(v)
|
|
||||||
else
|
|
||||||
copied[k] = v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return setmetatable(copied, getmetatable(t))
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.count_pairs(t)
|
|
||||||
local count = 0
|
|
||||||
|
|
||||||
for k, v in pairs(t) do
|
|
||||||
count = count + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
return count
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.random(t)
|
|
||||||
return t[math.random(1, #t)]
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.shuffle(t)
|
|
||||||
for i = #t, 2, -1 do
|
|
||||||
local j = math.random(i)
|
|
||||||
t[i], t[j] = t[j], t[i]
|
|
||||||
end
|
|
||||||
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.merge(t1, t2)
|
|
||||||
for i, v in pairs(t2) do
|
|
||||||
if type(i) == "number" then
|
|
||||||
t1[#t1 + 1] = v
|
|
||||||
elseif t1[i] == nil then
|
|
||||||
t1[i] = v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return t1
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.map(t, func)
|
|
||||||
for i, v in pairs(t) do
|
|
||||||
t[i] = func(i, v)
|
|
||||||
end
|
|
||||||
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.filter(t, func)
|
|
||||||
|
|
||||||
for i = #t, 1, -1 do
|
|
||||||
if not func(i, t[i]) then
|
|
||||||
table.remove(t, i)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local size = #t
|
|
||||||
|
|
||||||
for i, v in pairs(t) do
|
|
||||||
local i_type = type(i)
|
|
||||||
if i_type == "number" then
|
|
||||||
if i < 1 or i > size then
|
|
||||||
if not func(i, v) then
|
|
||||||
t[i] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if not func(i, v) then
|
|
||||||
t[i] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.set_default(t, key, default)
|
|
||||||
if t[key] == nil then
|
|
||||||
t[key] = default
|
|
||||||
return default
|
|
||||||
end
|
|
||||||
|
|
||||||
return t[key]
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.flat(t)
|
|
||||||
local flat = {}
|
|
||||||
|
|
||||||
for _, v in pairs(t) do
|
|
||||||
if type(v) == "table" then
|
|
||||||
table.merge(flat, v)
|
|
||||||
else
|
|
||||||
table.insert(flat, v)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return flat
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.deep_flat(t)
|
|
||||||
local flat = {}
|
|
||||||
|
|
||||||
for _, v in pairs(t) do
|
|
||||||
if type(v) == "table" then
|
|
||||||
table.merge(flat, table.deep_flat(v))
|
|
||||||
else
|
|
||||||
table.insert(flat, v)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return flat
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.sub(arr, start, stop)
|
|
||||||
local res = {}
|
|
||||||
start = start or 1
|
|
||||||
stop = stop or #arr
|
|
||||||
|
|
||||||
for i = start, stop do
|
|
||||||
table.insert(res, arr[i])
|
|
||||||
end
|
|
||||||
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
----------------------------------------------
|
|
||||||
|
|
||||||
local pattern_escape_replacements = {
|
|
||||||
["("] = "%(",
|
|
||||||
[")"] = "%)",
|
|
||||||
["."] = "%.",
|
|
||||||
["%"] = "%%",
|
|
||||||
["+"] = "%+",
|
|
||||||
["-"] = "%-",
|
|
||||||
["*"] = "%*",
|
|
||||||
["?"] = "%?",
|
|
||||||
["["] = "%[",
|
|
||||||
["]"] = "%]",
|
|
||||||
["^"] = "%^",
|
|
||||||
["$"] = "%$",
|
|
||||||
["\0"] = "%z"
|
|
||||||
}
|
|
||||||
|
|
||||||
function string.pattern_safe(str)
|
|
||||||
return string.gsub(str, ".", pattern_escape_replacements)
|
|
||||||
end
|
|
||||||
|
|
||||||
local string_sub = string.sub
|
|
||||||
local string_find = string.find
|
|
||||||
local string_len = string.len
|
|
||||||
function string.explode(separator, str, withpattern)
|
|
||||||
if (withpattern == nil) then withpattern = false end
|
|
||||||
|
|
||||||
local ret = {}
|
|
||||||
local current_pos = 1
|
|
||||||
|
|
||||||
for i = 1, string_len(str) do
|
|
||||||
local start_pos, end_pos = string_find(
|
|
||||||
str, separator, current_pos, not withpattern)
|
|
||||||
if (not start_pos) then break end
|
|
||||||
ret[i] = string_sub(str, current_pos, start_pos - 1)
|
|
||||||
current_pos = end_pos + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
ret[#ret + 1] = string_sub(str, current_pos)
|
|
||||||
|
|
||||||
return ret
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.split(str, delimiter)
|
|
||||||
return string.explode(delimiter, str)
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.formatted_time(seconds, format)
|
|
||||||
if (not seconds) then seconds = 0 end
|
|
||||||
local hours = math.floor(seconds / 3600)
|
|
||||||
local minutes = math.floor((seconds / 60) % 60)
|
|
||||||
local millisecs = (seconds - math.floor(seconds)) * 1000
|
|
||||||
seconds = math.floor(seconds % 60)
|
|
||||||
|
|
||||||
if (format) then
|
|
||||||
return string.format(format, minutes, seconds, millisecs)
|
|
||||||
else
|
|
||||||
return { h = hours, m = minutes, s = seconds, ms = millisecs }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.replace(str, tofind, toreplace)
|
|
||||||
local tbl = string.explode(tofind, str)
|
|
||||||
if (tbl[1]) then return table.concat(tbl, toreplace) end
|
|
||||||
return str
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.trim(s, char)
|
|
||||||
if char then char = string.pattern_safe(char) else char = "%s" end
|
|
||||||
return string.match(s, "^" .. char .. "*(.-)" .. char .. "*$") or s
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.trim_right(s, char)
|
|
||||||
if char then char = string.pattern_safe(char) else char = "%s" end
|
|
||||||
return string.match(s, "^(.-)" .. char .. "*$") or s
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.trim_left(s, char)
|
|
||||||
if char then char = string.pattern_safe(char) else char = "%s" end
|
|
||||||
return string.match(s, "^" .. char .. "*(.+)$") or s
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.pad(str, size, char)
|
|
||||||
char = char == nil and " " or char
|
|
||||||
|
|
||||||
local padding = math.floor((size - #str) / 2)
|
|
||||||
local extra_padding = (size - #str) % 2
|
|
||||||
|
|
||||||
return string.rep(char, padding) .. str .. string.rep(char, padding + extra_padding)
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.left_pad(str, size, char)
|
|
||||||
char = char == nil and " " or char
|
|
||||||
|
|
||||||
local left_padding = size - #str
|
|
||||||
return string.rep(char, left_padding) .. str
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.right_pad(str, size, char)
|
|
||||||
char = char == nil and " " or char
|
|
||||||
|
|
||||||
local right_padding = size - #str
|
|
||||||
return str .. string.rep(char, right_padding)
|
|
||||||
end
|
|
||||||
|
|
||||||
string.lower = utf8.lower
|
|
||||||
string.upper = utf8.upper
|
|
||||||
string.escape = utf8.escape
|
|
||||||
|
|
||||||
local meta = getmetatable("")
|
|
||||||
|
|
||||||
function meta:__index(key)
|
|
||||||
local val = string[key]
|
|
||||||
if (val ~= nil) then
|
|
||||||
return val
|
|
||||||
elseif (tonumber(key)) then
|
|
||||||
return string.sub(self, key, key)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.starts_with(str, start)
|
|
||||||
return string.sub(str, 1, string.len(start)) == start
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.ends_with(str, endStr)
|
|
||||||
return endStr == "" or string.sub(str, -string.len(endStr)) == endStr
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.has(t, x)
|
|
||||||
for i,v in ipairs(t) do
|
|
||||||
if v == x then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.index(t, x)
|
|
||||||
for i,v in ipairs(t) do
|
|
||||||
if v == x then
|
|
||||||
return i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return -1
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.remove_value(t, x)
|
|
||||||
local index = table.index(t, x)
|
|
||||||
if index ~= -1 then
|
|
||||||
table.remove(t, index)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.tostring(t)
|
|
||||||
local s = '['
|
|
||||||
for i,v in ipairs(t) do
|
|
||||||
s = s..tostring(v)
|
|
||||||
if i < #t then
|
|
||||||
s = s..', '
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return s..']'
|
|
||||||
end
|
|
||||||
|
|
||||||
function file.readlines(path)
|
|
||||||
local str = file.read(path)
|
|
||||||
local lines = {}
|
|
||||||
for s in str:gmatch("[^\r\n]+") do
|
|
||||||
table.insert(lines, s)
|
|
||||||
end
|
|
||||||
return lines
|
|
||||||
end
|
|
||||||
|
|
||||||
function debug.count_frames()
|
function debug.count_frames()
|
||||||
local frames = 1
|
local frames = 1
|
||||||
while true do
|
while true do
|
||||||
@ -768,7 +256,7 @@ function __vc__error(msg, frame, n, lastn)
|
|||||||
if events then
|
if events then
|
||||||
local frames = debug.get_traceback(1)
|
local frames = debug.get_traceback(1)
|
||||||
events.emit(
|
events.emit(
|
||||||
"core:error", msg,
|
"core:error", msg,
|
||||||
table.sub(frames, 1 + (n or 0), lastn and #frames-lastn)
|
table.sub(frames, 1 + (n or 0), lastn and #frames-lastn)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
@ -782,42 +270,20 @@ function __vc_warning(msg, detail, n)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function file.name(path)
|
require "core:internal/extensions/pack"
|
||||||
return path:match("([^:/\\]+)$")
|
require "core:internal/extensions/math"
|
||||||
end
|
require "core:internal/extensions/file"
|
||||||
|
require "core:internal/extensions/table"
|
||||||
|
require "core:internal/extensions/string"
|
||||||
|
|
||||||
function file.stem(path)
|
local bytearray = require "core:internal/bytearray"
|
||||||
local name = file.name(path)
|
Bytearray = bytearray.FFIBytearray
|
||||||
return name:match("(.+)%.[^%.]+$") or name
|
Bytearray_as_string = bytearray.FFIBytearray_as_string
|
||||||
end
|
U16view = bytearray.FFIU16view
|
||||||
|
I16view = bytearray.FFII16view
|
||||||
function file.ext(path)
|
U32view = bytearray.FFIU32view
|
||||||
return path:match("%.([^:/\\]+)$")
|
I32view = bytearray.FFII32view
|
||||||
end
|
Bytearray_construct = function(...) return Bytearray(...) end
|
||||||
|
|
||||||
function file.prefix(path)
|
|
||||||
return path:match("^([^:]+)")
|
|
||||||
end
|
|
||||||
|
|
||||||
function file.parent(path)
|
|
||||||
local dir = path:match("(.*)/")
|
|
||||||
if not dir then
|
|
||||||
return file.prefix(path)..":"
|
|
||||||
end
|
|
||||||
return dir
|
|
||||||
end
|
|
||||||
|
|
||||||
function file.path(path)
|
|
||||||
local pos = path:find(':')
|
|
||||||
return path:sub(pos + 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
function file.join(a, b)
|
|
||||||
if a[#a] == ':' then
|
|
||||||
return a .. b
|
|
||||||
end
|
|
||||||
return a .. "/" .. b
|
|
||||||
end
|
|
||||||
|
|
||||||
bit.compile = require "core:bitwise/compiler"
|
bit.compile = require "core:bitwise/compiler"
|
||||||
bit.execute = require "core:bitwise/executor"
|
bit.execute = require "core:bitwise/executor"
|
||||||
|
|||||||
@ -25,7 +25,8 @@ Grant %{0} pack modification permission?=Выдать разрешение на
|
|||||||
Error at line %{0}=Ошибка на строке %{0}
|
Error at line %{0}=Ошибка на строке %{0}
|
||||||
Run=Запустить
|
Run=Запустить
|
||||||
Filter=Фильтр
|
Filter=Фильтр
|
||||||
Are you sure you want to open the link: =Вы уверены, что хотите открыть ссылку:
|
Are you sure you want to open the link: =Вы уверены, что хотите открыть ссылку:
|
||||||
|
Grant '%{0}' pack audio recording permission?=Выдать паку '%{0}' разрешение на запись звука?
|
||||||
|
|
||||||
editor.info.tooltip=CTRL+S - Сохранить\nCTRL+R - Запустить\nCTRL+Z - Отменить\nCTRL+Y - Повторить
|
editor.info.tooltip=CTRL+S - Сохранить\nCTRL+R - Запустить\nCTRL+Z - Отменить\nCTRL+Y - Повторить
|
||||||
devtools.traceback=Стек вызовов (от последнего)
|
devtools.traceback=Стек вызовов (от последнего)
|
||||||
@ -107,6 +108,7 @@ settings.Shadows quality=Качество теней
|
|||||||
settings.Conflict=Найдены возможные конфликты
|
settings.Conflict=Найдены возможные конфликты
|
||||||
settings.Windowed=Оконный
|
settings.Windowed=Оконный
|
||||||
settings.Borderless=Безрамочный
|
settings.Borderless=Безрамочный
|
||||||
|
settings.Microphone=Микрофон
|
||||||
|
|
||||||
# Управление
|
# Управление
|
||||||
chunks.reload=Перезагрузить Чанки
|
chunks.reload=Перезагрузить Чанки
|
||||||
|
|||||||
@ -5,11 +5,41 @@
|
|||||||
|
|
||||||
#include "debug/Logger.hpp"
|
#include "debug/Logger.hpp"
|
||||||
#include "alutil.hpp"
|
#include "alutil.hpp"
|
||||||
|
#include "../MemoryPCMStream.hpp"
|
||||||
|
|
||||||
static debug::Logger logger("al-audio");
|
static debug::Logger logger("al-audio");
|
||||||
|
|
||||||
using namespace audio;
|
using namespace audio;
|
||||||
|
|
||||||
|
const char* alc_error_to_string(ALCenum error) {
|
||||||
|
switch (error) {
|
||||||
|
case ALC_NO_ERROR:
|
||||||
|
return "no error";
|
||||||
|
case ALC_INVALID_DEVICE:
|
||||||
|
return "invalid device handle";
|
||||||
|
case ALC_INVALID_CONTEXT:
|
||||||
|
return "invalid context handle";
|
||||||
|
case ALC_INVALID_ENUM:
|
||||||
|
return "invalid enum parameter passed to an ALC call";
|
||||||
|
case ALC_INVALID_VALUE:
|
||||||
|
return "invalid value parameter passed to an ALC call";
|
||||||
|
case ALC_OUT_OF_MEMORY:
|
||||||
|
return "out of memory";
|
||||||
|
default:
|
||||||
|
return "unknown ALC error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool check_alc_errors(ALCdevice* device, const char* context) {
|
||||||
|
ALCenum error = alcGetError(device);
|
||||||
|
if (error == ALC_NO_ERROR) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
logger.error() << context << ": " << alc_error_to_string(error) << "("
|
||||||
|
<< error << ")";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
ALSound::ALSound(
|
ALSound::ALSound(
|
||||||
ALAudio* al, uint buffer, const std::shared_ptr<PCM>& pcm, bool keepPCM
|
ALAudio* al, uint buffer, const std::shared_ptr<PCM>& pcm, bool keepPCM
|
||||||
)
|
)
|
||||||
@ -37,6 +67,70 @@ std::unique_ptr<Speaker> ALSound::newInstance(int priority, int channel) const {
|
|||||||
return speaker;
|
return speaker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ALInputDevice::ALInputDevice(
|
||||||
|
ALAudio* al,
|
||||||
|
ALCdevice* device,
|
||||||
|
uint channels,
|
||||||
|
uint bitsPerSample,
|
||||||
|
uint sampleRate
|
||||||
|
)
|
||||||
|
: al(al),
|
||||||
|
device(device),
|
||||||
|
channels(channels),
|
||||||
|
bitsPerSample(bitsPerSample),
|
||||||
|
sampleRate(sampleRate) {
|
||||||
|
const ALCchar* deviceName = alcGetString(device, ALC_CAPTURE_DEVICE_SPECIFIER);
|
||||||
|
|
||||||
|
if (deviceName) {
|
||||||
|
deviceSpecifier = std::string(deviceName);
|
||||||
|
} else {
|
||||||
|
logger.warning() << "could not retrieve input device specifier";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ALInputDevice::~ALInputDevice() {
|
||||||
|
alcCaptureCloseDevice(device);
|
||||||
|
check_alc_errors(device, "alcCaptureCloseDevice");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ALInputDevice::startCapture() {
|
||||||
|
alcCaptureStart(device);
|
||||||
|
check_alc_errors(device, "alcCaptureStart");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ALInputDevice::stopCapture() {
|
||||||
|
alcCaptureStop(device);
|
||||||
|
check_alc_errors(device, "alcCaptureStop");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint ALInputDevice::getChannels() const {
|
||||||
|
return channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint ALInputDevice::getSampleRate() const {
|
||||||
|
return sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint ALInputDevice::getBitsPerSample() const {
|
||||||
|
return bitsPerSample;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& ALInputDevice::getDeviceSpecifier() const {
|
||||||
|
return deviceSpecifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ALInputDevice::read(char* buffer, size_t bufferSize) {
|
||||||
|
ALCint samplesCount = 0;
|
||||||
|
alcGetIntegerv(device, ALC_CAPTURE_SAMPLES, sizeof(samplesCount), &samplesCount);
|
||||||
|
check_alc_errors(device, "alcGetIntegerv(ALC_CAPTURE_SAMPLES)");
|
||||||
|
size_t samplesRead = std::min<ALCsizei>(
|
||||||
|
samplesCount, bufferSize / channels / (bitsPerSample >> 3)
|
||||||
|
);
|
||||||
|
alcCaptureSamples(device, buffer, samplesRead);
|
||||||
|
check_alc_errors(device, "alcCaptureSamples");
|
||||||
|
return samplesRead * channels * (bitsPerSample >> 3);
|
||||||
|
}
|
||||||
|
|
||||||
ALStream::ALStream(
|
ALStream::ALStream(
|
||||||
ALAudio* al, std::shared_ptr<PCMStream> source, bool keepSource
|
ALAudio* al, std::shared_ptr<PCMStream> source, bool keepSource
|
||||||
)
|
)
|
||||||
@ -81,9 +175,10 @@ std::unique_ptr<Speaker> ALStream::createSpeaker(bool loop, int channel) {
|
|||||||
for (uint i = 0; i < ALStream::STREAM_BUFFERS; i++) {
|
for (uint i = 0; i < ALStream::STREAM_BUFFERS; i++) {
|
||||||
uint free_buffer = al->getFreeBuffer();
|
uint free_buffer = al->getFreeBuffer();
|
||||||
if (!preloadBuffer(free_buffer, loop)) {
|
if (!preloadBuffer(free_buffer, loop)) {
|
||||||
break;
|
unusedBuffers.push(free_buffer);
|
||||||
|
} else {
|
||||||
|
AL_CHECK(alSourceQueueBuffers(free_source, 1, &free_buffer));
|
||||||
}
|
}
|
||||||
AL_CHECK(alSourceQueueBuffers(free_source, 1, &free_buffer));
|
|
||||||
}
|
}
|
||||||
return std::make_unique<ALSpeaker>(al, free_source, PRIORITY_HIGH, channel);
|
return std::make_unique<ALSpeaker>(al, free_source, PRIORITY_HIGH, channel);
|
||||||
}
|
}
|
||||||
@ -130,11 +225,11 @@ void ALStream::unqueueBuffers(uint alsource) {
|
|||||||
uint ALStream::enqueueBuffers(uint alsource) {
|
uint ALStream::enqueueBuffers(uint alsource) {
|
||||||
uint preloaded = 0;
|
uint preloaded = 0;
|
||||||
if (!unusedBuffers.empty()) {
|
if (!unusedBuffers.empty()) {
|
||||||
uint first_buffer = unusedBuffers.front();
|
uint firstBuffer = unusedBuffers.front();
|
||||||
if (preloadBuffer(first_buffer, loop)) {
|
if (preloadBuffer(firstBuffer, loop)) {
|
||||||
preloaded++;
|
preloaded++;
|
||||||
unusedBuffers.pop();
|
unusedBuffers.pop();
|
||||||
AL_CHECK(alSourceQueueBuffers(alsource, 1, &first_buffer));
|
AL_CHECK(alSourceQueueBuffers(alsource, 1, &firstBuffer));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return preloaded;
|
return preloaded;
|
||||||
@ -144,14 +239,14 @@ void ALStream::update(double delta) {
|
|||||||
if (this->speaker == 0) {
|
if (this->speaker == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto p_speaker = audio::get_speaker(this->speaker);
|
auto speaker = audio::get_speaker(this->speaker);
|
||||||
if (p_speaker == nullptr) {
|
if (speaker == nullptr) {
|
||||||
this->speaker = 0;
|
this->speaker = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ALSpeaker* alspeaker = dynamic_cast<ALSpeaker*>(p_speaker);
|
ALSpeaker* alspeaker = dynamic_cast<ALSpeaker*>(speaker);
|
||||||
assert(alspeaker != nullptr);
|
assert(alspeaker != nullptr);
|
||||||
if (alspeaker->stopped) {
|
if (alspeaker->manuallyStopped) {
|
||||||
this->speaker = 0;
|
this->speaker = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -162,11 +257,11 @@ void ALStream::update(double delta) {
|
|||||||
uint preloaded = enqueueBuffers(alsource);
|
uint preloaded = enqueueBuffers(alsource);
|
||||||
|
|
||||||
// alspeaker->stopped is assigned to false at ALSpeaker::play(...)
|
// alspeaker->stopped is assigned to false at ALSpeaker::play(...)
|
||||||
if (p_speaker->isStopped() && !alspeaker->stopped) { //TODO: -V560 false-positive?
|
if (speaker->isStopped() && !alspeaker->manuallyStopped) { //TODO: -V560 false-positive?
|
||||||
if (preloaded) {
|
if (preloaded) {
|
||||||
p_speaker->play();
|
speaker->play();
|
||||||
} else {
|
} else if (isStopOnEnd()){
|
||||||
p_speaker->stop();
|
speaker->stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,6 +302,14 @@ void ALStream::setTime(duration_t time) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ALStream::isStopOnEnd() const {
|
||||||
|
return stopOnEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ALStream::setStopOnEnd(bool flag) {
|
||||||
|
stopOnEnd = flag;
|
||||||
|
}
|
||||||
|
|
||||||
ALSpeaker::ALSpeaker(ALAudio* al, uint source, int priority, int channel)
|
ALSpeaker::ALSpeaker(ALAudio* al, uint source, int priority, int channel)
|
||||||
: al(al), priority(priority), channel(channel), source(source) {
|
: al(al), priority(priority), channel(channel), source(source) {
|
||||||
}
|
}
|
||||||
@ -273,7 +376,7 @@ void ALSpeaker::setLoop(bool loop) {
|
|||||||
|
|
||||||
void ALSpeaker::play() {
|
void ALSpeaker::play() {
|
||||||
paused = false;
|
paused = false;
|
||||||
stopped = false;
|
manuallyStopped = false;
|
||||||
auto p_channel = get_channel(this->channel);
|
auto p_channel = get_channel(this->channel);
|
||||||
AL_CHECK(alSourcef(
|
AL_CHECK(alSourcef(
|
||||||
source,
|
source,
|
||||||
@ -289,7 +392,7 @@ void ALSpeaker::pause() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ALSpeaker::stop() {
|
void ALSpeaker::stop() {
|
||||||
stopped = true;
|
manuallyStopped = true;
|
||||||
if (source) {
|
if (source) {
|
||||||
AL_CHECK(alSourceStop(source));
|
AL_CHECK(alSourceStop(source));
|
||||||
|
|
||||||
@ -353,6 +456,13 @@ int ALSpeaker::getPriority() const {
|
|||||||
return priority;
|
return priority;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ALSpeaker::isManuallyStopped() const {
|
||||||
|
return manuallyStopped;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool alc_enumeration_ext = false;
|
||||||
|
|
||||||
ALAudio::ALAudio(ALCdevice* device, ALCcontext* context)
|
ALAudio::ALAudio(ALCdevice* device, ALCcontext* context)
|
||||||
: device(device), context(context) {
|
: device(device), context(context) {
|
||||||
ALCint size;
|
ALCint size;
|
||||||
@ -365,9 +475,15 @@ ALAudio::ALAudio(ALCdevice* device, ALCcontext* context)
|
|||||||
maxSources = attrs[i + 1];
|
maxSources = attrs[i + 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto devices = getAvailableDevices();
|
auto outputDevices = getOutputDeviceNames();
|
||||||
logger.info() << "devices:";
|
logger.info() << "output devices:";
|
||||||
for (auto& name : devices) {
|
for (auto& name : outputDevices) {
|
||||||
|
logger.info() << " " << name;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto inputDevices = getInputDeviceNames();
|
||||||
|
logger.info() << "input devices:";
|
||||||
|
for (auto& name : inputDevices) {
|
||||||
logger.info() << " " << name;
|
logger.info() << " " << name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -385,8 +501,10 @@ ALAudio::~ALAudio() {
|
|||||||
AL_CHECK(alDeleteBuffers(1, &buffer));
|
AL_CHECK(alDeleteBuffers(1, &buffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
AL_CHECK(alcMakeContextCurrent(context));
|
alcMakeContextCurrent(nullptr);
|
||||||
|
check_alc_errors(device, "alcMakeContextCurrent");
|
||||||
alcDestroyContext(context);
|
alcDestroyContext(context);
|
||||||
|
check_alc_errors(device, "alcDestroyContext");
|
||||||
if (!alcCloseDevice(device)) {
|
if (!alcCloseDevice(device)) {
|
||||||
logger.error() << "device not closed!";
|
logger.error() << "device not closed!";
|
||||||
}
|
}
|
||||||
@ -411,7 +529,71 @@ std::unique_ptr<Stream> ALAudio::openStream(
|
|||||||
return std::make_unique<ALStream>(this, stream, keepSource);
|
return std::make_unique<ALStream>(this, stream, keepSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> ALAudio::getInputDeviceNames() {
|
||||||
|
std::vector<std::string> devices;
|
||||||
|
|
||||||
|
if (!alc_enumeration_ext) {
|
||||||
|
logger.warning() << "enumeration extension is not available";
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto deviceList = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER);
|
||||||
|
if (deviceList == nullptr) {
|
||||||
|
logger.warning() << "no input devices found";
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
while (*deviceList) {
|
||||||
|
std::string deviceName(deviceList);
|
||||||
|
devices.push_back(deviceName);
|
||||||
|
deviceList += deviceName.length() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> ALAudio::getOutputDeviceNames() {
|
||||||
|
std::vector<std::string> devices;
|
||||||
|
|
||||||
|
if (!alc_enumeration_ext) {
|
||||||
|
logger.warning() << "enumeration extension is not available";
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto deviceList = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER);
|
||||||
|
if (deviceList == nullptr) {
|
||||||
|
logger.warning() << "no input devices found";
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
while (*deviceList) {
|
||||||
|
std::string deviceName(deviceList);
|
||||||
|
devices.push_back(deviceName);
|
||||||
|
deviceList += deviceName.length() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<InputDevice> ALAudio::openInputDevice(
|
||||||
|
const std::string& deviceName, uint sampleRate, uint channels, uint bitsPerSample
|
||||||
|
) {
|
||||||
|
uint bps = bitsPerSample >> 3;
|
||||||
|
ALCdevice* device = alcCaptureOpenDevice(
|
||||||
|
deviceName.empty() ? nullptr : deviceName.c_str(),
|
||||||
|
sampleRate,
|
||||||
|
AL::to_al_format(channels, bitsPerSample),
|
||||||
|
sampleRate * channels * bps / 8
|
||||||
|
);
|
||||||
|
if (check_alc_errors(device, "alcCaptureOpenDevice"))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return std::make_unique<ALInputDevice>(
|
||||||
|
this, device, channels, bitsPerSample, sampleRate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<ALAudio> ALAudio::create() {
|
std::unique_ptr<ALAudio> ALAudio::create() {
|
||||||
|
alc_enumeration_ext = alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT");
|
||||||
|
|
||||||
ALCdevice* device = alcOpenDevice(nullptr);
|
ALCdevice* device = alcOpenDevice(nullptr);
|
||||||
if (device == nullptr) return nullptr;
|
if (device == nullptr) return nullptr;
|
||||||
ALCcontext* context = alcCreateContext(device, nullptr);
|
ALCcontext* context = alcCreateContext(device, nullptr);
|
||||||
@ -468,24 +650,6 @@ void ALAudio::freeBuffer(uint buffer) {
|
|||||||
freebuffers.push_back(buffer);
|
freebuffers.push_back(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> ALAudio::getAvailableDevices() const {
|
|
||||||
std::vector<std::string> devicesVec;
|
|
||||||
|
|
||||||
const ALCchar* devices;
|
|
||||||
devices = alcGetString(device, ALC_DEVICE_SPECIFIER);
|
|
||||||
if (!AL_GET_ERROR()) {
|
|
||||||
return devicesVec;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* ptr = devices;
|
|
||||||
do {
|
|
||||||
devicesVec.emplace_back(ptr);
|
|
||||||
ptr += devicesVec.back().size() + 1;
|
|
||||||
} while (ptr[0]);
|
|
||||||
|
|
||||||
return devicesVec;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ALAudio::setListener(
|
void ALAudio::setListener(
|
||||||
glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up
|
glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up
|
||||||
) {
|
) {
|
||||||
|
|||||||
@ -58,6 +58,7 @@ namespace audio {
|
|||||||
bool keepSource;
|
bool keepSource;
|
||||||
char buffer[BUFFER_SIZE];
|
char buffer[BUFFER_SIZE];
|
||||||
bool loop = false;
|
bool loop = false;
|
||||||
|
bool stopOnEnd = false;
|
||||||
|
|
||||||
bool preloadBuffer(uint buffer, bool loop);
|
bool preloadBuffer(uint buffer, bool loop);
|
||||||
void unqueueBuffers(uint alsource);
|
void unqueueBuffers(uint alsource);
|
||||||
@ -80,6 +81,39 @@ namespace audio {
|
|||||||
void setTime(duration_t time) override;
|
void setTime(duration_t time) override;
|
||||||
|
|
||||||
static inline constexpr uint STREAM_BUFFERS = 3;
|
static inline constexpr uint STREAM_BUFFERS = 3;
|
||||||
|
|
||||||
|
bool isStopOnEnd() const override;
|
||||||
|
|
||||||
|
void setStopOnEnd(bool stopOnEnd) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ALInputDevice : public InputDevice {
|
||||||
|
public:
|
||||||
|
ALInputDevice(
|
||||||
|
ALAudio* al,
|
||||||
|
ALCdevice* device,
|
||||||
|
uint channels,
|
||||||
|
uint bitsPerSample,
|
||||||
|
uint sampleRate
|
||||||
|
);
|
||||||
|
~ALInputDevice() override;
|
||||||
|
|
||||||
|
void startCapture() override;
|
||||||
|
void stopCapture() override;
|
||||||
|
|
||||||
|
uint getChannels() const override;
|
||||||
|
uint getSampleRate() const override;
|
||||||
|
uint getBitsPerSample() const override;
|
||||||
|
const std::string& getDeviceSpecifier() const override;
|
||||||
|
|
||||||
|
size_t read(char* buffer, size_t bufferSize) override;
|
||||||
|
private:
|
||||||
|
ALAudio* al;
|
||||||
|
ALCdevice* device;
|
||||||
|
uint channels;
|
||||||
|
uint bitsPerSample;
|
||||||
|
uint sampleRate;
|
||||||
|
std::string deviceSpecifier;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// @brief AL source adapter
|
/// @brief AL source adapter
|
||||||
@ -90,7 +124,7 @@ namespace audio {
|
|||||||
float volume = 0.0f;
|
float volume = 0.0f;
|
||||||
public:
|
public:
|
||||||
ALStream* stream = nullptr;
|
ALStream* stream = nullptr;
|
||||||
bool stopped = true;
|
bool manuallyStopped = true;
|
||||||
bool paused = false;
|
bool paused = false;
|
||||||
uint source;
|
uint source;
|
||||||
duration_t duration = 0.0f;
|
duration_t duration = 0.0f;
|
||||||
@ -130,6 +164,8 @@ namespace audio {
|
|||||||
bool isRelative() const override;
|
bool isRelative() const override;
|
||||||
|
|
||||||
int getPriority() const override;
|
int getPriority() const override;
|
||||||
|
|
||||||
|
bool isManuallyStopped() const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ALAudio : public Backend {
|
class ALAudio : public Backend {
|
||||||
@ -152,15 +188,24 @@ namespace audio {
|
|||||||
void freeSource(uint source);
|
void freeSource(uint source);
|
||||||
void freeBuffer(uint buffer);
|
void freeBuffer(uint buffer);
|
||||||
|
|
||||||
std::vector<std::string> getAvailableDevices() const;
|
|
||||||
|
|
||||||
std::unique_ptr<Sound> createSound(
|
std::unique_ptr<Sound> createSound(
|
||||||
std::shared_ptr<PCM> pcm, bool keepPCM
|
std::shared_ptr<PCM> pcm, bool keepPCM
|
||||||
) override;
|
) override;
|
||||||
|
|
||||||
std::unique_ptr<Stream> openStream(
|
std::unique_ptr<Stream> openStream(
|
||||||
std::shared_ptr<PCMStream> stream, bool keepSource
|
std::shared_ptr<PCMStream> stream, bool keepSource
|
||||||
) override;
|
) override;
|
||||||
|
|
||||||
|
std::unique_ptr<InputDevice> openInputDevice(
|
||||||
|
const std::string& deviceName,
|
||||||
|
uint sampleRate,
|
||||||
|
uint channels,
|
||||||
|
uint bitsPerSample
|
||||||
|
) override;
|
||||||
|
|
||||||
|
std::vector<std::string> getOutputDeviceNames() override;
|
||||||
|
std::vector<std::string> getInputDeviceNames() override;
|
||||||
|
|
||||||
void setListener(
|
void setListener(
|
||||||
glm::vec3 position,
|
glm::vec3 position,
|
||||||
glm::vec3 velocity,
|
glm::vec3 velocity,
|
||||||
|
|||||||
67
src/audio/MemoryPCMStream.cpp
Normal file
67
src/audio/MemoryPCMStream.cpp
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#include "MemoryPCMStream.hpp"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
using namespace audio;
|
||||||
|
|
||||||
|
MemoryPCMStream::MemoryPCMStream(
|
||||||
|
uint sampleRate, uint channels, uint bitsPerSample
|
||||||
|
)
|
||||||
|
: sampleRate(sampleRate), channels(channels), bitsPerSample(bitsPerSample) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryPCMStream::feed(util::span<ubyte> bytes) {
|
||||||
|
buffer.insert(buffer.end(), bytes.begin(), bytes.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MemoryPCMStream::isOpen() const {
|
||||||
|
return open;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryPCMStream::close() {
|
||||||
|
open = false;
|
||||||
|
buffer = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t MemoryPCMStream::read(char* dst, size_t bufferSize) {
|
||||||
|
if (!open) {
|
||||||
|
return PCMStream::ERROR;
|
||||||
|
}
|
||||||
|
if (buffer.empty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t count = std::min<size_t>(bufferSize, buffer.size());
|
||||||
|
std::memcpy(dst, buffer.data(), count);
|
||||||
|
buffer.erase(buffer.begin(), buffer.begin() + count);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t MemoryPCMStream::getTotalSamples() const {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
duration_t MemoryPCMStream::getTotalDuration() const {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint MemoryPCMStream::getChannels() const {
|
||||||
|
return channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint MemoryPCMStream::getSampleRate() const {
|
||||||
|
return sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint MemoryPCMStream::getBitsPerSample() const {
|
||||||
|
return bitsPerSample;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MemoryPCMStream::isSeekable() const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryPCMStream::seek(size_t position) {}
|
||||||
|
|
||||||
|
size_t MemoryPCMStream::available() const {
|
||||||
|
return buffer.size();
|
||||||
|
}
|
||||||
44
src/audio/MemoryPCMStream.hpp
Normal file
44
src/audio/MemoryPCMStream.hpp
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "audio.hpp"
|
||||||
|
#include "util/span.hpp"
|
||||||
|
|
||||||
|
namespace audio {
|
||||||
|
class MemoryPCMStream : public PCMStream {
|
||||||
|
public:
|
||||||
|
MemoryPCMStream(uint sampleRate, uint channels, uint bitsPerSample);
|
||||||
|
|
||||||
|
void feed(util::span<ubyte> bytes);
|
||||||
|
|
||||||
|
bool isOpen() const override;
|
||||||
|
|
||||||
|
void close() override;
|
||||||
|
|
||||||
|
size_t read(char* buffer, size_t bufferSize) override;
|
||||||
|
|
||||||
|
size_t getTotalSamples() const override;
|
||||||
|
|
||||||
|
duration_t getTotalDuration() const override;
|
||||||
|
|
||||||
|
uint getChannels() const override;
|
||||||
|
|
||||||
|
uint getSampleRate() const override;
|
||||||
|
|
||||||
|
uint getBitsPerSample() const override;
|
||||||
|
|
||||||
|
bool isSeekable() const override;
|
||||||
|
|
||||||
|
void seek(size_t position) override;
|
||||||
|
|
||||||
|
size_t available() const;
|
||||||
|
private:
|
||||||
|
uint sampleRate;
|
||||||
|
uint channels;
|
||||||
|
uint bitsPerSample;
|
||||||
|
bool open = true;
|
||||||
|
|
||||||
|
std::vector<ubyte> buffer;
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -61,6 +61,13 @@ namespace audio {
|
|||||||
|
|
||||||
void setTime(duration_t time) override {
|
void setTime(duration_t time) override {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isStopOnEnd() const override {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setStopOnEnd(bool stopOnEnd) override {
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class NoAudio : public Backend {
|
class NoAudio : public Backend {
|
||||||
@ -71,10 +78,24 @@ namespace audio {
|
|||||||
std::unique_ptr<Sound> createSound(
|
std::unique_ptr<Sound> createSound(
|
||||||
std::shared_ptr<PCM> pcm, bool keepPCM
|
std::shared_ptr<PCM> pcm, bool keepPCM
|
||||||
) override;
|
) override;
|
||||||
|
|
||||||
std::unique_ptr<Stream> openStream(
|
std::unique_ptr<Stream> openStream(
|
||||||
std::shared_ptr<PCMStream> stream, bool keepSource
|
std::shared_ptr<PCMStream> stream, bool keepSource
|
||||||
) override;
|
) override;
|
||||||
|
|
||||||
|
std::unique_ptr<InputDevice> openInputDevice(
|
||||||
|
const std::string& deviceName, uint sampleRate, uint channels, uint bitsPerSample
|
||||||
|
) override {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> getInputDeviceNames() override {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
std::vector<std::string> getOutputDeviceNames() override {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
void setListener(
|
void setListener(
|
||||||
glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up
|
glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up
|
||||||
) override {
|
) override {
|
||||||
|
|||||||
@ -151,6 +151,8 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static std::unique_ptr<InputDevice> input_device = nullptr;
|
||||||
|
|
||||||
void audio::initialize(bool enabled, AudioSettings& settings) {
|
void audio::initialize(bool enabled, AudioSettings& settings) {
|
||||||
enabled = enabled && settings.enabled.get();
|
enabled = enabled && settings.enabled.get();
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
@ -180,6 +182,15 @@ void audio::initialize(bool enabled, AudioSettings& settings) {
|
|||||||
audio::get_channel(channel.name)->setVolume(value * value);
|
audio::get_channel(channel.name)->setVolume(value * value);
|
||||||
}, true));
|
}, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input_device = backend->openInputDevice("", 44100, 1, 16);
|
||||||
|
if (input_device) {
|
||||||
|
input_device->startCapture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InputDevice* audio::get_input_device() {
|
||||||
|
return input_device.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<PCM> audio::load_PCM(const io::path& file, bool headerOnly) {
|
std::unique_ptr<PCM> audio::load_PCM(const io::path& file, bool headerOnly) {
|
||||||
@ -242,6 +253,38 @@ std::unique_ptr<Stream> audio::open_stream(
|
|||||||
return backend->openStream(std::move(stream), keepSource);
|
return backend->openStream(std::move(stream), keepSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<InputDevice> audio::open_input_device(
|
||||||
|
const std::string& deviceName, uint sampleRate, uint channels, uint bitsPerSample
|
||||||
|
) {
|
||||||
|
return backend->openInputDevice(
|
||||||
|
deviceName, sampleRate, channels, bitsPerSample
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> audio::get_input_devices_names() {
|
||||||
|
return backend->getInputDeviceNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> audio::get_output_devices_names() {
|
||||||
|
return backend->getOutputDeviceNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio::set_input_device(const std::string& deviceName) {
|
||||||
|
auto newDevice = backend->openInputDevice(deviceName, 44100, 1, 16);
|
||||||
|
if (newDevice == nullptr) {
|
||||||
|
logger.error() << "could not open input device: " << deviceName;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input_device) {
|
||||||
|
input_device->stopCapture();
|
||||||
|
}
|
||||||
|
input_device = std::move(newDevice);
|
||||||
|
if (input_device) {
|
||||||
|
input_device->startCapture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void audio::set_listener(
|
void audio::set_listener(
|
||||||
glm::vec3 position, glm::vec3 velocity, glm::vec3 lookAt, glm::vec3 up
|
glm::vec3 position, glm::vec3 velocity, glm::vec3 lookAt, glm::vec3 up
|
||||||
) {
|
) {
|
||||||
@ -421,8 +464,15 @@ void audio::update(double delta) {
|
|||||||
speaker->update(channel);
|
speaker->update(channel);
|
||||||
}
|
}
|
||||||
if (speaker->isStopped()) {
|
if (speaker->isStopped()) {
|
||||||
streams.erase(it->first);
|
auto foundStream = streams.find(it->first);
|
||||||
it = speakers.erase(it);
|
if (foundStream == streams.end() ||
|
||||||
|
(!speaker->isManuallyStopped() &&
|
||||||
|
foundStream->second->isStopOnEnd())) {
|
||||||
|
streams.erase(it->first);
|
||||||
|
it = speakers.erase(it);
|
||||||
|
} else {
|
||||||
|
it++;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
it++;
|
it++;
|
||||||
}
|
}
|
||||||
@ -458,6 +508,9 @@ void audio::reset_channel(int index) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void audio::close() {
|
void audio::close() {
|
||||||
|
if (input_device) {
|
||||||
|
input_device->stopCapture();
|
||||||
|
}
|
||||||
speakers.clear();
|
speakers.clear();
|
||||||
delete backend;
|
delete backend;
|
||||||
backend = nullptr;
|
backend = nullptr;
|
||||||
|
|||||||
@ -24,6 +24,8 @@ namespace audio {
|
|||||||
/// @brief streams and important sounds
|
/// @brief streams and important sounds
|
||||||
constexpr inline int PRIORITY_HIGH = 10;
|
constexpr inline int PRIORITY_HIGH = 10;
|
||||||
|
|
||||||
|
constexpr inline size_t MAX_INPUT_SAMPLES = 22050;
|
||||||
|
|
||||||
class Speaker;
|
class Speaker;
|
||||||
|
|
||||||
/// @brief Audio speaker states
|
/// @brief Audio speaker states
|
||||||
@ -108,6 +110,31 @@ namespace audio {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class InputDevice {
|
||||||
|
public:
|
||||||
|
virtual ~InputDevice() {};
|
||||||
|
|
||||||
|
virtual void startCapture() = 0;
|
||||||
|
virtual void stopCapture() = 0;
|
||||||
|
|
||||||
|
/// @brief Get number of audio channels
|
||||||
|
/// @return 1 if mono, 2 if stereo
|
||||||
|
virtual uint getChannels() const = 0;
|
||||||
|
/// @brief Get audio sampling frequency
|
||||||
|
/// @return number of mono samples per second
|
||||||
|
virtual uint getSampleRate() const = 0;
|
||||||
|
/// @brief Get number of bits per mono sample
|
||||||
|
/// @return 8 or 16
|
||||||
|
virtual uint getBitsPerSample() const = 0;
|
||||||
|
|
||||||
|
/// @brief Read available data to buffer.
|
||||||
|
/// @return size of data received or PCMStream::ERROR in case of error
|
||||||
|
virtual size_t read(char* buffer, size_t bufferSize) = 0;
|
||||||
|
|
||||||
|
/// @brief Get device specifier string
|
||||||
|
virtual const std::string& getDeviceSpecifier() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
/// @brief audio::PCMStream is a data source for audio::Stream
|
/// @brief audio::PCMStream is a data source for audio::Stream
|
||||||
class PCMStream {
|
class PCMStream {
|
||||||
public:
|
public:
|
||||||
@ -121,6 +148,10 @@ namespace audio {
|
|||||||
/// (always equals bufferSize if seekable and looped)
|
/// (always equals bufferSize if seekable and looped)
|
||||||
virtual size_t readFully(char* buffer, size_t bufferSize, bool loop);
|
virtual size_t readFully(char* buffer, size_t bufferSize, bool loop);
|
||||||
|
|
||||||
|
/// @brief Read available data to buffer
|
||||||
|
/// @param buffer destination buffer
|
||||||
|
/// @param bufferSize destination buffer size
|
||||||
|
/// @return count of received bytes or PCMStream::ERROR
|
||||||
virtual size_t read(char* buffer, size_t bufferSize) = 0;
|
virtual size_t read(char* buffer, size_t bufferSize) = 0;
|
||||||
|
|
||||||
/// @brief Close stream
|
/// @brief Close stream
|
||||||
@ -195,6 +226,9 @@ namespace audio {
|
|||||||
/// @brief Set playhead to the selected time
|
/// @brief Set playhead to the selected time
|
||||||
/// @param time selected time
|
/// @param time selected time
|
||||||
virtual void setTime(duration_t time) = 0;
|
virtual void setTime(duration_t time) = 0;
|
||||||
|
|
||||||
|
virtual bool isStopOnEnd() const = 0;
|
||||||
|
virtual void setStopOnEnd(bool stopOnEnd) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// @brief Sound is an audio asset that supposed to support many
|
/// @brief Sound is an audio asset that supposed to support many
|
||||||
@ -329,6 +363,8 @@ namespace audio {
|
|||||||
inline bool isStopped() const {
|
inline bool isStopped() const {
|
||||||
return getState() == State::stopped;
|
return getState() == State::stopped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual bool isManuallyStopped() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Backend {
|
class Backend {
|
||||||
@ -341,12 +377,20 @@ namespace audio {
|
|||||||
virtual std::unique_ptr<Stream> openStream(
|
virtual std::unique_ptr<Stream> openStream(
|
||||||
std::shared_ptr<PCMStream> stream, bool keepSource
|
std::shared_ptr<PCMStream> stream, bool keepSource
|
||||||
) = 0;
|
) = 0;
|
||||||
|
virtual std::unique_ptr<InputDevice> openInputDevice(
|
||||||
|
const std::string& deviceName,
|
||||||
|
uint sampleRate,
|
||||||
|
uint channels,
|
||||||
|
uint bitsPerSample
|
||||||
|
) = 0;
|
||||||
virtual void setListener(
|
virtual void setListener(
|
||||||
glm::vec3 position,
|
glm::vec3 position,
|
||||||
glm::vec3 velocity,
|
glm::vec3 velocity,
|
||||||
glm::vec3 lookAt,
|
glm::vec3 lookAt,
|
||||||
glm::vec3 up
|
glm::vec3 up
|
||||||
) = 0;
|
) = 0;
|
||||||
|
virtual std::vector<std::string> getInputDeviceNames() = 0;
|
||||||
|
virtual std::vector<std::string> getOutputDeviceNames() = 0;
|
||||||
virtual void update(double delta) = 0;
|
virtual void update(double delta) = 0;
|
||||||
|
|
||||||
/// @brief Check if backend is an abstraction that does not internally
|
/// @brief Check if backend is an abstraction that does not internally
|
||||||
@ -402,6 +446,28 @@ namespace audio {
|
|||||||
std::shared_ptr<PCMStream> stream, bool keepSource
|
std::shared_ptr<PCMStream> stream, bool keepSource
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// @brief Open audio input device
|
||||||
|
/// @param sampleRate sample rate
|
||||||
|
/// @param channels channels count (1 - mono, 2 - stereo)
|
||||||
|
/// @param bitsPerSample number of bits per sample (8 or 16)
|
||||||
|
/// @return new InputDevice instance or nullptr
|
||||||
|
std::unique_ptr<InputDevice> open_input_device(
|
||||||
|
const std::string& deviceName,
|
||||||
|
uint sampleRate,
|
||||||
|
uint channels,
|
||||||
|
uint bitsPerSample
|
||||||
|
);
|
||||||
|
|
||||||
|
/// @brief Retrieve names of available audio input devices
|
||||||
|
/// @return list of device names
|
||||||
|
std::vector<std::string> get_input_devices_names();
|
||||||
|
|
||||||
|
/// @brief Retrieve names of available audio output devices
|
||||||
|
/// @return list of device names
|
||||||
|
std::vector<std::string> get_output_devices_names();
|
||||||
|
|
||||||
|
void set_input_device(const std::string& deviceName);
|
||||||
|
|
||||||
/// @brief Configure 3D listener
|
/// @brief Configure 3D listener
|
||||||
/// @param position listener position
|
/// @param position listener position
|
||||||
/// @param velocity listener velocity (used for Doppler effect)
|
/// @param velocity listener velocity (used for Doppler effect)
|
||||||
@ -515,6 +581,8 @@ namespace audio {
|
|||||||
/// @brief Stop all playing audio in channel, reset channel state
|
/// @brief Stop all playing audio in channel, reset channel state
|
||||||
void reset_channel(int channel);
|
void reset_channel(int channel);
|
||||||
|
|
||||||
|
InputDevice* get_input_device();
|
||||||
|
|
||||||
/// @brief Finalize audio system
|
/// @brief Finalize audio system
|
||||||
void close();
|
void close();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -420,6 +420,99 @@ void ImageData::fixAlphaColor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void check_matching(const ImageData& a, const ImageData& b) {
|
||||||
|
if (b.getWidth() != a.getWidth() ||
|
||||||
|
b.getHeight() != a.getHeight() ||
|
||||||
|
b.getFormat() != a.getFormat()) {
|
||||||
|
throw std::runtime_error("image sizes or formats do not match");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageData::mulColor(const glm::ivec4& color) {
|
||||||
|
uint comps;
|
||||||
|
switch (format) {
|
||||||
|
case ImageFormat::rgb888: comps = 3; break;
|
||||||
|
case ImageFormat::rgba8888: comps = 4; break;
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("only unsigned byte formats supported");
|
||||||
|
}
|
||||||
|
for (uint y = 0; y < height; y++) {
|
||||||
|
for (uint x = 0; x < width; x++) {
|
||||||
|
uint idx = (y * width + x) * comps;
|
||||||
|
for (uint c = 0; c < comps; c++) {
|
||||||
|
float val = static_cast<float>(data[idx + c]) * color[c] / 255.0f;
|
||||||
|
data[idx + c] =
|
||||||
|
static_cast<ubyte>(std::min(std::max(val, 0.0f), 255.0f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageData::addColor(const ImageData& other, int multiplier) {
|
||||||
|
check_matching(*this, other);
|
||||||
|
|
||||||
|
uint comps;
|
||||||
|
switch (format) {
|
||||||
|
case ImageFormat::rgb888: comps = 3; break;
|
||||||
|
case ImageFormat::rgba8888: comps = 4; break;
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("only unsigned byte formats supported");
|
||||||
|
}
|
||||||
|
for (uint y = 0; y < height; y++) {
|
||||||
|
for (uint x = 0; x < width; x++) {
|
||||||
|
uint idx = (y * width + x) * comps;
|
||||||
|
for (uint c = 0; c < comps; c++) {
|
||||||
|
int val = data[idx + c] + other.data[idx + c] * multiplier;
|
||||||
|
data[idx + c] =
|
||||||
|
static_cast<ubyte>(std::min(std::max(val, 0), 255));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageData::addColor(const glm::ivec4& color, int multiplier) {
|
||||||
|
uint comps;
|
||||||
|
switch (format) {
|
||||||
|
case ImageFormat::rgb888: comps = 3; break;
|
||||||
|
case ImageFormat::rgba8888: comps = 4; break;
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("only unsigned byte formats supported");
|
||||||
|
}
|
||||||
|
for (uint y = 0; y < height; y++) {
|
||||||
|
for (uint x = 0; x < width; x++) {
|
||||||
|
uint idx = (y * width + x) * comps;
|
||||||
|
for (uint c = 0; c < comps; c++) {
|
||||||
|
int val = data[idx + c] + color[c] * multiplier;
|
||||||
|
data[idx + c] =
|
||||||
|
static_cast<ubyte>(std::min(std::max(val, 0), 255));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageData::mulColor(const ImageData& other) {
|
||||||
|
check_matching(*this, other);
|
||||||
|
|
||||||
|
uint comps;
|
||||||
|
switch (format) {
|
||||||
|
case ImageFormat::rgb888: comps = 3; break;
|
||||||
|
case ImageFormat::rgba8888: comps = 4; break;
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("only unsigned byte formats supported");
|
||||||
|
}
|
||||||
|
for (uint y = 0; y < height; y++) {
|
||||||
|
for (uint x = 0; x < width; x++) {
|
||||||
|
uint idx = (y * width + x) * comps;
|
||||||
|
for (uint c = 0; c < comps; c++) {
|
||||||
|
float val = static_cast<float>(data[idx + c]) *
|
||||||
|
static_cast<float>(other.data[idx + c]) / 255.0f;
|
||||||
|
data[idx + c] =
|
||||||
|
static_cast<ubyte>(std::min(std::max(val, 0.0f), 255.0f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<ImageData> add_atlas_margins(ImageData* image, int grid_size) {
|
std::unique_ptr<ImageData> add_atlas_margins(ImageData* image, int grid_size) {
|
||||||
// RGBA is only supported
|
// RGBA is only supported
|
||||||
assert(image->getFormat() == ImageFormat::rgba8888);
|
assert(image->getFormat() == ImageFormat::rgba8888);
|
||||||
|
|||||||
@ -32,6 +32,10 @@ public:
|
|||||||
void blit(const ImageData& image, int x, int y);
|
void blit(const ImageData& image, int x, int y);
|
||||||
void extrude(int x, int y, int w, int h);
|
void extrude(int x, int y, int w, int h);
|
||||||
void fixAlphaColor();
|
void fixAlphaColor();
|
||||||
|
void mulColor(const glm::ivec4& color);
|
||||||
|
void mulColor(const ImageData& other);
|
||||||
|
void addColor(const glm::ivec4& color, int multiplier);
|
||||||
|
void addColor(const ImageData& other, int multiplier);
|
||||||
|
|
||||||
std::unique_ptr<ImageData> cropped(int x, int y, int width, int height) const;
|
std::unique_ptr<ImageData> cropped(int x, int y, int width, int height) const;
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
#include "graphics/core/Texture.hpp"
|
#include "graphics/core/Texture.hpp"
|
||||||
#include "graphics/core/Atlas.hpp"
|
#include "graphics/core/Atlas.hpp"
|
||||||
#include "util/Buffer.hpp"
|
#include "util/Buffer.hpp"
|
||||||
#include "../lua_custom_types.hpp"
|
#include "../usertypes/lua_type_canvas.hpp"
|
||||||
|
|
||||||
using namespace scripting;
|
using namespace scripting;
|
||||||
|
|
||||||
|
|||||||
@ -68,6 +68,26 @@ inline audio::speakerid_t play_stream(
|
|||||||
if (channel == -1) {
|
if (channel == -1) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
if (!scripting::engine->isHeadless()) {
|
||||||
|
auto assets = scripting::engine->getAssets();
|
||||||
|
|
||||||
|
auto stream = assets->getShared<audio::PCMStream>(filename);
|
||||||
|
if (stream) {
|
||||||
|
return audio::play(
|
||||||
|
audio::open_stream(std::move(stream), true),
|
||||||
|
glm::vec3(
|
||||||
|
static_cast<float>(x),
|
||||||
|
static_cast<float>(y),
|
||||||
|
static_cast<float>(z)
|
||||||
|
),
|
||||||
|
relative,
|
||||||
|
volume,
|
||||||
|
pitch,
|
||||||
|
loop,
|
||||||
|
channel
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
io::path file;
|
io::path file;
|
||||||
if (std::strchr(filename, ':')) {
|
if (std::strchr(filename, ':')) {
|
||||||
file = std::string(filename);
|
file = std::string(filename);
|
||||||
@ -360,16 +380,80 @@ static int l_audio_get_velocity(lua::State* L) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @brief audio.count_speakers() -> integer
|
/// @brief audio.count_speakers() -> integer
|
||||||
static int l_audio_count_speakers(lua::State* L) {
|
static int l_audio_count_speakers(lua::State* L) {
|
||||||
return lua::pushinteger(L, audio::count_speakers());
|
return lua::pushinteger(L, audio::count_speakers());
|
||||||
}
|
}
|
||||||
|
|
||||||
// @brief audio.count_streams() -> integer
|
/// @brief audio.count_streams() -> integer
|
||||||
static int l_audio_count_streams(lua::State* L) {
|
static int l_audio_count_streams(lua::State* L) {
|
||||||
return lua::pushinteger(L, audio::count_streams());
|
return lua::pushinteger(L, audio::count_streams());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief audio.input.fetch(size) -> Bytearray
|
||||||
|
static int l_audio_fetch_input(lua::State* L) {
|
||||||
|
auto device = audio::get_input_device();
|
||||||
|
if (device == nullptr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t size = lua::touinteger(L, 1);
|
||||||
|
const size_t MAX_BUFFER_SIZE = audio::MAX_INPUT_SAMPLES * 4;
|
||||||
|
if (size == 0) {
|
||||||
|
size = MAX_BUFFER_SIZE;
|
||||||
|
}
|
||||||
|
size = std::min<size_t>(size, MAX_BUFFER_SIZE);
|
||||||
|
ubyte buffer[MAX_BUFFER_SIZE];
|
||||||
|
size = device->read(reinterpret_cast<char*>(buffer), size);
|
||||||
|
|
||||||
|
std::vector<ubyte> bytes(buffer, buffer + size);
|
||||||
|
return lua::create_bytearray(L, std::move(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int l_audio_get_input_devices_names(lua::State* L) {
|
||||||
|
auto device_names = audio::get_input_devices_names();
|
||||||
|
lua::createtable(L, device_names.size(), 0);
|
||||||
|
int index = 1;
|
||||||
|
for (const auto& name : device_names) {
|
||||||
|
lua::pushstring(L, name.c_str());
|
||||||
|
lua::rawseti(L, index++);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int l_audio_get_output_devices_names(lua::State* L) {
|
||||||
|
auto device_names = audio::get_output_devices_names();
|
||||||
|
lua::createtable(L, device_names.size(), 0);
|
||||||
|
int index = 1;
|
||||||
|
for (const auto& name : device_names) {
|
||||||
|
lua::pushstring(L, name.c_str());
|
||||||
|
lua::rawseti(L, index++);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int l_audio_set_input_device(lua::State* L) {
|
||||||
|
auto device_name = lua::tostring(L, 1);
|
||||||
|
audio::set_input_device(device_name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int l_audio_get_input_info(lua::State* L) {
|
||||||
|
auto device = audio::get_input_device();
|
||||||
|
if (device == nullptr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
lua::createtable(L, 0, 4);
|
||||||
|
lua::pushlstring(L, device->getDeviceSpecifier());
|
||||||
|
lua::setfield(L, "device_specifier");
|
||||||
|
lua::pushinteger(L, device->getChannels());
|
||||||
|
lua::setfield(L, "channels");
|
||||||
|
lua::pushinteger(L, device->getSampleRate());
|
||||||
|
lua::setfield(L, "sample_rate");
|
||||||
|
lua::pushinteger(L, device->getBitsPerSample());
|
||||||
|
lua::setfield(L, "bits_per_sample");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
const luaL_Reg audiolib[] = {
|
const luaL_Reg audiolib[] = {
|
||||||
{"play_sound", lua::wrap<l_audio_play_sound>},
|
{"play_sound", lua::wrap<l_audio_play_sound>},
|
||||||
{"play_sound_2d", lua::wrap<l_audio_play_sound_2d>},
|
{"play_sound_2d", lua::wrap<l_audio_play_sound_2d>},
|
||||||
@ -395,5 +479,10 @@ const luaL_Reg audiolib[] = {
|
|||||||
{"get_velocity", lua::wrap<l_audio_get_velocity>},
|
{"get_velocity", lua::wrap<l_audio_get_velocity>},
|
||||||
{"count_speakers", lua::wrap<l_audio_count_speakers>},
|
{"count_speakers", lua::wrap<l_audio_count_speakers>},
|
||||||
{"count_streams", lua::wrap<l_audio_count_streams>},
|
{"count_streams", lua::wrap<l_audio_count_streams>},
|
||||||
|
{"__fetch_input", lua::wrap<l_audio_fetch_input>},
|
||||||
|
{"get_input_devices_names", lua::wrap<l_audio_get_input_devices_names>},
|
||||||
|
{"get_output_devices_names", lua::wrap<l_audio_get_output_devices_names>},
|
||||||
|
{"set_input_device", lua::wrap<l_audio_set_input_device>},
|
||||||
|
{"get_input_info", lua::wrap<l_audio_get_input_info>},
|
||||||
{nullptr, nullptr}
|
{nullptr, nullptr}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
#include "coders/binary_json.hpp"
|
#include "coders/binary_json.hpp"
|
||||||
#include "api_lua.hpp"
|
#include "api_lua.hpp"
|
||||||
#include "util/Buffer.hpp"
|
#include "util/Buffer.hpp"
|
||||||
#include "../lua_custom_types.hpp"
|
|
||||||
|
|
||||||
static int l_tobytes(lua::State* L) {
|
static int l_tobytes(lua::State* L) {
|
||||||
auto value = lua::tovalue(L, 1);
|
auto value = lua::tovalue(L, 1);
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
#include "content/ContentControl.hpp"
|
#include "content/ContentControl.hpp"
|
||||||
#include "engine/Engine.hpp"
|
#include "engine/Engine.hpp"
|
||||||
#include "engine/EnginePaths.hpp"
|
#include "engine/EnginePaths.hpp"
|
||||||
#include "../lua_custom_types.hpp"
|
#include "../usertypes/lua_type_voxelfragment.hpp"
|
||||||
|
|
||||||
using namespace scripting;
|
using namespace scripting;
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,7 @@
|
|||||||
#include "items/Inventories.hpp"
|
#include "items/Inventories.hpp"
|
||||||
#include "util/stringutil.hpp"
|
#include "util/stringutil.hpp"
|
||||||
#include "world/Level.hpp"
|
#include "world/Level.hpp"
|
||||||
|
#include "../usertypes/lua_type_canvas.hpp"
|
||||||
|
|
||||||
using namespace gui;
|
using namespace gui;
|
||||||
using namespace scripting;
|
using namespace scripting;
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cwctype>
|
#include <cwctype>
|
||||||
|
|
||||||
#include "../lua_custom_types.hpp"
|
|
||||||
#include "util/stringutil.hpp"
|
#include "util/stringutil.hpp"
|
||||||
|
|
||||||
static int l_tobytes(lua::State* L) {
|
static int l_tobytes(lua::State* L) {
|
||||||
|
|||||||
@ -48,4 +48,9 @@ namespace lua {
|
|||||||
|
|
||||||
void log_error(const std::string& text);
|
void log_error(const std::string& text);
|
||||||
|
|
||||||
|
class Userdata {
|
||||||
|
public:
|
||||||
|
virtual ~Userdata() {};
|
||||||
|
virtual const std::string& getTypeName() const = 0;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,140 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include <array>
|
|
||||||
#include <random>
|
|
||||||
|
|
||||||
#include "lua_commons.hpp"
|
|
||||||
#include "constants.hpp"
|
|
||||||
#include "maths/UVRegion.hpp"
|
|
||||||
|
|
||||||
struct fnl_state;
|
|
||||||
class Heightmap;
|
|
||||||
class VoxelFragment;
|
|
||||||
class Texture;
|
|
||||||
class ImageData;
|
|
||||||
|
|
||||||
namespace lua {
|
|
||||||
class Userdata {
|
|
||||||
public:
|
|
||||||
virtual ~Userdata() {};
|
|
||||||
virtual const std::string& getTypeName() const = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class LuaHeightmap : public Userdata {
|
|
||||||
std::shared_ptr<Heightmap> map;
|
|
||||||
std::unique_ptr<fnl_state> noise;
|
|
||||||
public:
|
|
||||||
LuaHeightmap(const std::shared_ptr<Heightmap>& map);
|
|
||||||
LuaHeightmap(uint width, uint height);
|
|
||||||
|
|
||||||
virtual ~LuaHeightmap();
|
|
||||||
|
|
||||||
uint getWidth() const;
|
|
||||||
|
|
||||||
uint getHeight() const;
|
|
||||||
|
|
||||||
float* getValues();
|
|
||||||
|
|
||||||
const float* getValues() const;
|
|
||||||
|
|
||||||
const std::string& getTypeName() const override {
|
|
||||||
return TYPENAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::shared_ptr<Heightmap>& getHeightmap() const {
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
fnl_state* getNoise() {
|
|
||||||
return noise.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setSeed(int64_t seed);
|
|
||||||
|
|
||||||
static int createMetatable(lua::State*);
|
|
||||||
inline static std::string TYPENAME = "Heightmap";
|
|
||||||
};
|
|
||||||
static_assert(!std::is_abstract<LuaHeightmap>());
|
|
||||||
|
|
||||||
class LuaVoxelFragment : public Userdata {
|
|
||||||
std::array<std::shared_ptr<VoxelFragment>, 4> fragmentVariants;
|
|
||||||
public:
|
|
||||||
LuaVoxelFragment(
|
|
||||||
std::array<std::shared_ptr<VoxelFragment>, 4> fragmentVariants
|
|
||||||
);
|
|
||||||
|
|
||||||
virtual ~LuaVoxelFragment();
|
|
||||||
|
|
||||||
std::shared_ptr<VoxelFragment> getFragment(size_t rotation) const {
|
|
||||||
return fragmentVariants.at(rotation & 0b11);
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& getTypeName() const override {
|
|
||||||
return TYPENAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int createMetatable(lua::State*);
|
|
||||||
inline static std::string TYPENAME = "VoxelFragment";
|
|
||||||
};
|
|
||||||
static_assert(!std::is_abstract<LuaVoxelFragment>());
|
|
||||||
|
|
||||||
class LuaCanvas : public Userdata {
|
|
||||||
public:
|
|
||||||
explicit LuaCanvas(
|
|
||||||
std::shared_ptr<Texture> texture,
|
|
||||||
std::shared_ptr<ImageData> data,
|
|
||||||
UVRegion region = UVRegion(0, 0, 1, 1)
|
|
||||||
);
|
|
||||||
~LuaCanvas() override = default;
|
|
||||||
|
|
||||||
const std::string& getTypeName() const override {
|
|
||||||
return TYPENAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] auto& getTexture() const {
|
|
||||||
return *texture;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] auto& getData() const {
|
|
||||||
return *data;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] bool hasTexture() const {
|
|
||||||
return texture != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto shareTexture() const {
|
|
||||||
return texture;
|
|
||||||
}
|
|
||||||
|
|
||||||
void update(int extrusion = ATLAS_EXTRUSION);
|
|
||||||
|
|
||||||
void createTexture();
|
|
||||||
|
|
||||||
static int createMetatable(lua::State*);
|
|
||||||
inline static std::string TYPENAME = "Canvas";
|
|
||||||
private:
|
|
||||||
std::shared_ptr<Texture> texture; // nullable
|
|
||||||
std::shared_ptr<ImageData> data;
|
|
||||||
UVRegion region;
|
|
||||||
};
|
|
||||||
static_assert(!std::is_abstract<LuaCanvas>());
|
|
||||||
|
|
||||||
class LuaRandom : public Userdata {
|
|
||||||
public:
|
|
||||||
std::mt19937 rng;
|
|
||||||
|
|
||||||
explicit LuaRandom(uint64_t seed) : rng(seed) {}
|
|
||||||
virtual ~LuaRandom() override = default;
|
|
||||||
|
|
||||||
const std::string& getTypeName() const override {
|
|
||||||
return TYPENAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int createMetatable(lua::State*);
|
|
||||||
inline static std::string TYPENAME = "__vc_Random";
|
|
||||||
};
|
|
||||||
static_assert(!std::is_abstract<LuaRandom>());
|
|
||||||
}
|
|
||||||
@ -8,7 +8,11 @@
|
|||||||
#include "debug/Logger.hpp"
|
#include "debug/Logger.hpp"
|
||||||
#include "util/stringutil.hpp"
|
#include "util/stringutil.hpp"
|
||||||
#include "libs/api_lua.hpp"
|
#include "libs/api_lua.hpp"
|
||||||
#include "lua_custom_types.hpp"
|
#include "usertypes/lua_type_heightmap.hpp"
|
||||||
|
#include "usertypes/lua_type_voxelfragment.hpp"
|
||||||
|
#include "usertypes/lua_type_canvas.hpp"
|
||||||
|
#include "usertypes/lua_type_random.hpp"
|
||||||
|
#include "usertypes/lua_type_pcmstream.hpp"
|
||||||
#include "engine/Engine.hpp"
|
#include "engine/Engine.hpp"
|
||||||
|
|
||||||
static debug::Logger logger("lua-state");
|
static debug::Logger logger("lua-state");
|
||||||
@ -181,5 +185,12 @@ State* lua::create_state(const EnginePaths& paths, StateType stateType) {
|
|||||||
}
|
}
|
||||||
pop(L);
|
pop(L);
|
||||||
}
|
}
|
||||||
|
newusertype<LuaPCMStream>(L);
|
||||||
|
if (getglobal(L, "audio")) {
|
||||||
|
if (getglobal(L, "__vc_PCMStream")) {
|
||||||
|
setfield(L, "PCMStream");
|
||||||
|
}
|
||||||
|
pop(L);
|
||||||
|
}
|
||||||
return L;
|
return L;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -155,18 +155,18 @@ static int l_error_handler(lua_State* L) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int lua::call(State* L, int argc, int nresults) {
|
int lua::call(State* L, int argc, int nresults) {
|
||||||
int handler_pos = gettop(L) - argc;
|
int handlerPos = gettop(L) - argc;
|
||||||
pushcfunction(L, l_error_handler);
|
pushcfunction(L, l_error_handler);
|
||||||
insert(L, handler_pos);
|
insert(L, handlerPos);
|
||||||
int top = gettop(L);
|
int top = gettop(L);
|
||||||
if (lua_pcall(L, argc, nresults, handler_pos)) {
|
if (lua_pcall(L, argc, nresults, handlerPos)) {
|
||||||
std::string log = tostring(L, -1);
|
std::string log = tostring(L, -1);
|
||||||
pop(L);
|
pop(L);
|
||||||
remove(L, handler_pos);
|
remove(L, handlerPos);
|
||||||
throw luaerror(log);
|
throw luaerror(log);
|
||||||
}
|
}
|
||||||
int added = gettop(L) - (top - argc - 1);
|
int added = gettop(L) - (top - argc - 1);
|
||||||
remove(L, handler_pos);
|
remove(L, handlerPos);
|
||||||
return added;
|
return added;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,6 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "data/dv.hpp"
|
#include "data/dv.hpp"
|
||||||
#include "lua_custom_types.hpp"
|
|
||||||
#include "lua_wrapper.hpp"
|
#include "lua_wrapper.hpp"
|
||||||
#define GLM_ENABLE_EXPERIMENTAL
|
#define GLM_ENABLE_EXPERIMENTAL
|
||||||
#include <glm/gtx/quaternion.hpp>
|
#include <glm/gtx/quaternion.hpp>
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
#include <unordered_map>
|
#include "lua_type_canvas.hpp"
|
||||||
|
|
||||||
#include "graphics/core/ImageData.hpp"
|
#include "graphics/core/ImageData.hpp"
|
||||||
#include "graphics/core/Texture.hpp"
|
#include "graphics/core/Texture.hpp"
|
||||||
#include "logic/scripting/lua/lua_custom_types.hpp"
|
|
||||||
#include "logic/scripting/lua/lua_util.hpp"
|
#include "logic/scripting/lua/lua_util.hpp"
|
||||||
#include "engine/Engine.hpp"
|
#include "engine/Engine.hpp"
|
||||||
#include "assets/Assets.hpp"
|
#include "assets/Assets.hpp"
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
using namespace lua;
|
using namespace lua;
|
||||||
|
|
||||||
LuaCanvas::LuaCanvas(
|
LuaCanvas::LuaCanvas(
|
||||||
@ -46,7 +47,9 @@ void LuaCanvas::update(int extrusion) {
|
|||||||
h + extrusion * 2
|
h + extrusion * 2
|
||||||
);
|
);
|
||||||
extruded->blit(*data, extrusion, extrusion);
|
extruded->blit(*data, extrusion, extrusion);
|
||||||
extruded->extrude(0, 0, w + extrusion * 2, h + extrusion * 2);
|
for (uint j = 0; j < extrusion; j++) {
|
||||||
|
extruded->extrude(extrusion - j, extrusion - j, w + j*2, h + j*2);
|
||||||
|
}
|
||||||
texture->reloadPartial(
|
texture->reloadPartial(
|
||||||
*extruded,
|
*extruded,
|
||||||
x - extrusion,
|
x - extrusion,
|
||||||
@ -65,6 +68,10 @@ void LuaCanvas::createTexture() {
|
|||||||
texture->setMipMapping(false, true);
|
texture->setMipMapping(false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LuaCanvas::unbindTexture() {
|
||||||
|
texture.reset();
|
||||||
|
}
|
||||||
|
|
||||||
union RGBA {
|
union RGBA {
|
||||||
struct {
|
struct {
|
||||||
uint8_t r, g, b, a;
|
uint8_t r, g, b, a;
|
||||||
@ -98,7 +105,13 @@ static int l_at(State* L) {
|
|||||||
if (auto pixel = get_at(L, x, y)) {
|
if (auto pixel = get_at(L, x, y)) {
|
||||||
return pushinteger(L, pixel->rgba);
|
return pushinteger(L, pixel->rgba);
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int l_unbind_texture(State* L) {
|
||||||
|
if (auto canvas = touserdata<LuaCanvas>(L, 1)) {
|
||||||
|
canvas->unbindTexture();
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,6 +242,48 @@ static int l_create_texture(State* L) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int l_mul(State* L) {
|
||||||
|
auto canvas = touserdata<LuaCanvas>(L, 1);
|
||||||
|
if (canvas == nullptr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (lua::isnumber(L, 2)) {
|
||||||
|
RGBA rgba = get_rgba(L, 2);
|
||||||
|
canvas->getData().mulColor(glm::ivec4 {rgba.r, rgba.g, rgba.b, rgba.a});
|
||||||
|
} else if (auto other = touserdata<LuaCanvas>(L, 2)) {
|
||||||
|
canvas->getData().mulColor(other->getData());
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int l_add(State* L) {
|
||||||
|
auto canvas = touserdata<LuaCanvas>(L, 1);
|
||||||
|
if (canvas == nullptr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (lua::istable(L, 2)) {
|
||||||
|
RGBA rgba = get_rgba(L, 2);
|
||||||
|
canvas->getData().addColor(glm::ivec4 {rgba.r, rgba.g, rgba.b, rgba.a}, 1);
|
||||||
|
} else if (auto other = touserdata<LuaCanvas>(L, 2)) {
|
||||||
|
canvas->getData().addColor(other->getData(), 1);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int l_sub(State* L) {
|
||||||
|
auto canvas = touserdata<LuaCanvas>(L, 1);
|
||||||
|
if (canvas == nullptr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (lua::istable(L, 2)) {
|
||||||
|
RGBA rgba = get_rgba(L, 2);
|
||||||
|
canvas->getData().addColor(glm::ivec4 {rgba.r, rgba.g, rgba.b, rgba.a}, -1);
|
||||||
|
} else if (auto other = touserdata<LuaCanvas>(L, 2)) {
|
||||||
|
canvas->getData().addColor(other->getData(), -1);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static std::unordered_map<std::string, lua_CFunction> methods {
|
static std::unordered_map<std::string, lua_CFunction> methods {
|
||||||
{"at", lua::wrap<l_at>},
|
{"at", lua::wrap<l_at>},
|
||||||
{"set", lua::wrap<l_set>},
|
{"set", lua::wrap<l_set>},
|
||||||
@ -237,6 +292,10 @@ static std::unordered_map<std::string, lua_CFunction> methods {
|
|||||||
{"clear", lua::wrap<l_clear>},
|
{"clear", lua::wrap<l_clear>},
|
||||||
{"update", lua::wrap<l_update>},
|
{"update", lua::wrap<l_update>},
|
||||||
{"create_texture", lua::wrap<l_create_texture>},
|
{"create_texture", lua::wrap<l_create_texture>},
|
||||||
|
{"unbind_texture", lua::wrap<l_unbind_texture>},
|
||||||
|
{"mul", lua::wrap<l_mul>},
|
||||||
|
{"add", lua::wrap<l_add>},
|
||||||
|
{"sub", lua::wrap<l_sub>},
|
||||||
{"_set_data", lua::wrap<l_set_data>},
|
{"_set_data", lua::wrap<l_set_data>},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
53
src/logic/scripting/lua/usertypes/lua_type_canvas.hpp
Normal file
53
src/logic/scripting/lua/usertypes/lua_type_canvas.hpp
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../lua_commons.hpp"
|
||||||
|
#include "maths/UVRegion.hpp"
|
||||||
|
#include "constants.hpp"
|
||||||
|
|
||||||
|
class Texture;
|
||||||
|
class ImageData;
|
||||||
|
|
||||||
|
namespace lua {
|
||||||
|
class LuaCanvas : public Userdata {
|
||||||
|
public:
|
||||||
|
explicit LuaCanvas(
|
||||||
|
std::shared_ptr<Texture> texture,
|
||||||
|
std::shared_ptr<ImageData> data,
|
||||||
|
UVRegion region = UVRegion(0, 0, 1, 1)
|
||||||
|
);
|
||||||
|
~LuaCanvas() override = default;
|
||||||
|
|
||||||
|
const std::string& getTypeName() const override {
|
||||||
|
return TYPENAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto& getTexture() const {
|
||||||
|
return *texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto& getData() const {
|
||||||
|
return *data;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool hasTexture() const {
|
||||||
|
return texture != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto shareTexture() const {
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(int extrusion = ATLAS_EXTRUSION);
|
||||||
|
|
||||||
|
void createTexture();
|
||||||
|
void unbindTexture();
|
||||||
|
|
||||||
|
static int createMetatable(lua::State*);
|
||||||
|
inline static std::string TYPENAME = "Canvas";
|
||||||
|
private:
|
||||||
|
std::shared_ptr<Texture> texture; // nullable
|
||||||
|
std::shared_ptr<ImageData> data;
|
||||||
|
UVRegion region;
|
||||||
|
};
|
||||||
|
static_assert(!std::is_abstract<LuaCanvas>());
|
||||||
|
}
|
||||||
@ -1,9 +1,4 @@
|
|||||||
#include "../lua_custom_types.hpp"
|
#include "lua_type_heightmap.hpp"
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include <sstream>
|
|
||||||
#include <iomanip>
|
|
||||||
#include <filesystem>
|
|
||||||
|
|
||||||
#include "util/functional_util.hpp"
|
#include "util/functional_util.hpp"
|
||||||
#define FNL_IMPL
|
#define FNL_IMPL
|
||||||
@ -15,6 +10,12 @@
|
|||||||
#include "engine/Engine.hpp"
|
#include "engine/Engine.hpp"
|
||||||
#include "engine/EnginePaths.hpp"
|
#include "engine/EnginePaths.hpp"
|
||||||
#include "../lua_util.hpp"
|
#include "../lua_util.hpp"
|
||||||
|
#include "lua_type_heightmap.hpp"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
using namespace lua;
|
using namespace lua;
|
||||||
|
|
||||||
|
|||||||
44
src/logic/scripting/lua/usertypes/lua_type_heightmap.hpp
Normal file
44
src/logic/scripting/lua/usertypes/lua_type_heightmap.hpp
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../lua_commons.hpp"
|
||||||
|
|
||||||
|
struct fnl_state;
|
||||||
|
class Heightmap;
|
||||||
|
|
||||||
|
namespace lua {
|
||||||
|
class LuaHeightmap : public Userdata {
|
||||||
|
std::shared_ptr<Heightmap> map;
|
||||||
|
std::unique_ptr<fnl_state> noise;
|
||||||
|
public:
|
||||||
|
LuaHeightmap(const std::shared_ptr<Heightmap>& map);
|
||||||
|
LuaHeightmap(uint width, uint height);
|
||||||
|
|
||||||
|
virtual ~LuaHeightmap();
|
||||||
|
|
||||||
|
uint getWidth() const;
|
||||||
|
|
||||||
|
uint getHeight() const;
|
||||||
|
|
||||||
|
float* getValues();
|
||||||
|
|
||||||
|
const float* getValues() const;
|
||||||
|
|
||||||
|
const std::string& getTypeName() const override {
|
||||||
|
return TYPENAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::shared_ptr<Heightmap>& getHeightmap() const {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
fnl_state* getNoise() {
|
||||||
|
return noise.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSeed(int64_t seed);
|
||||||
|
|
||||||
|
static int createMetatable(lua::State*);
|
||||||
|
inline static std::string TYPENAME = "Heightmap";
|
||||||
|
};
|
||||||
|
static_assert(!std::is_abstract<LuaHeightmap>());
|
||||||
|
}
|
||||||
117
src/logic/scripting/lua/usertypes/lua_type_pcmstream.cpp
Normal file
117
src/logic/scripting/lua/usertypes/lua_type_pcmstream.cpp
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
#include "../lua_util.hpp"
|
||||||
|
#include "lua_type_pcmstream.hpp"
|
||||||
|
#include "assets/Assets.hpp"
|
||||||
|
#include "audio/MemoryPCMStream.hpp"
|
||||||
|
#include "engine/Engine.hpp"
|
||||||
|
|
||||||
|
using namespace lua;
|
||||||
|
using namespace audio;
|
||||||
|
using namespace scripting;
|
||||||
|
|
||||||
|
LuaPCMStream::LuaPCMStream(std::shared_ptr<audio::MemoryPCMStream>&& stream)
|
||||||
|
: stream(std::move(stream)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
LuaPCMStream::~LuaPCMStream() = default;
|
||||||
|
|
||||||
|
const std::shared_ptr<audio::MemoryPCMStream>& LuaPCMStream::getStream() const {
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int l_feed(lua::State* L) {
|
||||||
|
auto stream = touserdata<LuaPCMStream>(L, 1);
|
||||||
|
if (stream == nullptr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
auto bytes = bytearray_as_string(L, 2);
|
||||||
|
stream->getStream()->feed(
|
||||||
|
{reinterpret_cast<const ubyte*>(bytes.data()), bytes.size()}
|
||||||
|
);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int l_share(lua::State* L) {
|
||||||
|
auto stream = touserdata<LuaPCMStream>(L, 1);
|
||||||
|
if (stream == nullptr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
auto alias = require_lstring(L, 2);
|
||||||
|
if (engine->isHeadless()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
auto assets = engine->getAssets();
|
||||||
|
assets->store<PCMStream>(stream->getStream(), std::string(alias));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int l_create_sound(lua::State* L) {
|
||||||
|
auto stream = touserdata<LuaPCMStream>(L, 1);
|
||||||
|
if (stream == nullptr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
auto alias = require_lstring(L, 2);
|
||||||
|
auto memoryStream = stream->getStream();
|
||||||
|
|
||||||
|
std::vector<char> buffer(memoryStream->available());
|
||||||
|
memoryStream->readFully(buffer.data(), buffer.size(), true);
|
||||||
|
|
||||||
|
auto pcm = std::make_shared<PCM>(
|
||||||
|
std::move(buffer),
|
||||||
|
0,
|
||||||
|
memoryStream->getChannels(),
|
||||||
|
static_cast<uint8_t>(memoryStream->getBitsPerSample()),
|
||||||
|
memoryStream->getSampleRate(),
|
||||||
|
memoryStream->isSeekable()
|
||||||
|
);
|
||||||
|
auto sound = audio::create_sound(std::move(pcm), true);
|
||||||
|
auto assets = engine->getAssets();
|
||||||
|
assets->store<audio::Sound>(std::move(sound), std::string(alias));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::unordered_map<std::string, lua_CFunction> methods {
|
||||||
|
{"feed", lua::wrap<l_feed>},
|
||||||
|
{"share", lua::wrap<l_share>},
|
||||||
|
{"create_sound", lua::wrap<l_create_sound>},
|
||||||
|
};
|
||||||
|
|
||||||
|
static int l_meta_meta_call(lua::State* L) {
|
||||||
|
auto sampleRate = touinteger(L, 2);
|
||||||
|
auto channels = touinteger(L, 3);
|
||||||
|
auto bitsPerSample = touinteger(L, 4);
|
||||||
|
auto stream =
|
||||||
|
std::make_shared<MemoryPCMStream>(sampleRate, channels, bitsPerSample);
|
||||||
|
return newuserdata<LuaPCMStream>(L, std::move(stream));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int l_meta_tostring(lua::State* L) {
|
||||||
|
return pushstring(L, "PCMStream");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int l_meta_index(lua::State* L) {
|
||||||
|
auto stream = touserdata<LuaPCMStream>(L, 1);
|
||||||
|
if (stream == nullptr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (isstring(L, 2)) {
|
||||||
|
auto found = methods.find(tostring(L, 2));
|
||||||
|
if (found != methods.end()) {
|
||||||
|
return pushcfunction(L, found->second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaPCMStream::createMetatable(lua::State* L) {
|
||||||
|
createtable(L, 0, 3);
|
||||||
|
pushcfunction(L, lua::wrap<l_meta_tostring>);
|
||||||
|
setfield(L, "__tostring");
|
||||||
|
pushcfunction(L, lua::wrap<l_meta_index>);
|
||||||
|
setfield(L, "__index");
|
||||||
|
|
||||||
|
createtable(L, 0, 1);
|
||||||
|
pushcfunction(L, lua::wrap<l_meta_meta_call>);
|
||||||
|
setfield(L, "__call");
|
||||||
|
setmetatable(L);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
26
src/logic/scripting/lua/usertypes/lua_type_pcmstream.hpp
Normal file
26
src/logic/scripting/lua/usertypes/lua_type_pcmstream.hpp
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../lua_commons.hpp"
|
||||||
|
|
||||||
|
namespace audio {
|
||||||
|
class MemoryPCMStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace lua {
|
||||||
|
class LuaPCMStream : public Userdata {
|
||||||
|
public:
|
||||||
|
explicit LuaPCMStream(std::shared_ptr<audio::MemoryPCMStream>&& stream);
|
||||||
|
virtual ~LuaPCMStream() override;
|
||||||
|
|
||||||
|
const std::shared_ptr<audio::MemoryPCMStream>& getStream() const;
|
||||||
|
|
||||||
|
const std::string& getTypeName() const override {
|
||||||
|
return TYPENAME;
|
||||||
|
}
|
||||||
|
static int createMetatable(lua::State*);
|
||||||
|
inline static std::string TYPENAME = "__vc_PCMStream";
|
||||||
|
private:
|
||||||
|
std::shared_ptr<audio::MemoryPCMStream> stream;
|
||||||
|
};
|
||||||
|
static_assert(!std::is_abstract<LuaPCMStream>());
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
#include "../lua_custom_types.hpp"
|
|
||||||
#include "../lua_util.hpp"
|
#include "../lua_util.hpp"
|
||||||
|
#include "lua_type_random.hpp"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
|
|||||||
23
src/logic/scripting/lua/usertypes/lua_type_random.hpp
Normal file
23
src/logic/scripting/lua/usertypes/lua_type_random.hpp
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../lua_commons.hpp"
|
||||||
|
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
namespace lua {
|
||||||
|
class LuaRandom : public Userdata {
|
||||||
|
public:
|
||||||
|
std::mt19937 rng;
|
||||||
|
|
||||||
|
explicit LuaRandom(uint64_t seed) : rng(seed) {}
|
||||||
|
virtual ~LuaRandom() override = default;
|
||||||
|
|
||||||
|
const std::string& getTypeName() const override {
|
||||||
|
return TYPENAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int createMetatable(lua::State*);
|
||||||
|
inline static std::string TYPENAME = "__vc_Random";
|
||||||
|
};
|
||||||
|
static_assert(!std::is_abstract<LuaRandom>());
|
||||||
|
}
|
||||||
@ -1,7 +1,6 @@
|
|||||||
#include "../lua_custom_types.hpp"
|
#include "lua_type_voxelfragment.hpp"
|
||||||
|
|
||||||
#include "../lua_util.hpp"
|
#include "../lua_util.hpp"
|
||||||
|
|
||||||
#include "world/generator/VoxelFragment.hpp"
|
#include "world/generator/VoxelFragment.hpp"
|
||||||
#include "util/stringutil.hpp"
|
#include "util/stringutil.hpp"
|
||||||
#include "world/Level.hpp"
|
#include "world/Level.hpp"
|
||||||
|
|||||||
31
src/logic/scripting/lua/usertypes/lua_type_voxelfragment.hpp
Normal file
31
src/logic/scripting/lua/usertypes/lua_type_voxelfragment.hpp
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include "../lua_commons.hpp"
|
||||||
|
|
||||||
|
class VoxelFragment;
|
||||||
|
|
||||||
|
namespace lua {
|
||||||
|
class LuaVoxelFragment : public Userdata {
|
||||||
|
std::array<std::shared_ptr<VoxelFragment>, 4> fragmentVariants;
|
||||||
|
public:
|
||||||
|
LuaVoxelFragment(
|
||||||
|
std::array<std::shared_ptr<VoxelFragment>, 4> fragmentVariants
|
||||||
|
);
|
||||||
|
|
||||||
|
virtual ~LuaVoxelFragment();
|
||||||
|
|
||||||
|
std::shared_ptr<VoxelFragment> getFragment(size_t rotation) const {
|
||||||
|
return fragmentVariants.at(rotation & 0b11);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& getTypeName() const override {
|
||||||
|
return TYPENAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int createMetatable(lua::State*);
|
||||||
|
inline static std::string TYPENAME = "VoxelFragment";
|
||||||
|
};
|
||||||
|
static_assert(!std::is_abstract<LuaVoxelFragment>());
|
||||||
|
}
|
||||||
@ -17,7 +17,6 @@
|
|||||||
#include "logic/BlocksController.hpp"
|
#include "logic/BlocksController.hpp"
|
||||||
#include "logic/LevelController.hpp"
|
#include "logic/LevelController.hpp"
|
||||||
#include "lua/lua_engine.hpp"
|
#include "lua/lua_engine.hpp"
|
||||||
#include "lua/lua_custom_types.hpp"
|
|
||||||
#include "maths/Heightmap.hpp"
|
#include "maths/Heightmap.hpp"
|
||||||
#include "objects/Player.hpp"
|
#include "objects/Player.hpp"
|
||||||
#include "util/stringutil.hpp"
|
#include "util/stringutil.hpp"
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
#include "scripting_commons.hpp"
|
#include "scripting_commons.hpp"
|
||||||
#include "typedefs.hpp"
|
#include "typedefs.hpp"
|
||||||
#include "lua/lua_engine.hpp"
|
#include "lua/lua_engine.hpp"
|
||||||
#include "lua/lua_custom_types.hpp"
|
#include "lua/usertypes/lua_type_heightmap.hpp"
|
||||||
#include "content/Content.hpp"
|
#include "content/Content.hpp"
|
||||||
#include "voxels/Block.hpp"
|
#include "voxels/Block.hpp"
|
||||||
#include "voxels/Chunk.hpp"
|
#include "voxels/Chunk.hpp"
|
||||||
|
|||||||
42
src/util/span.hpp
Normal file
42
src/util/span.hpp
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace util {
|
||||||
|
template <typename T>
|
||||||
|
class span {
|
||||||
|
public:
|
||||||
|
constexpr span(const T* ptr, size_t length)
|
||||||
|
: ptr(ptr), length(length) {}
|
||||||
|
|
||||||
|
const T& operator[](size_t index) const {
|
||||||
|
return ptr[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
const T& at(size_t index) const {
|
||||||
|
if (index >= length) {
|
||||||
|
throw std::out_of_range("index is out of range");
|
||||||
|
}
|
||||||
|
return ptr[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
auto begin() const {
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto end() const {
|
||||||
|
return ptr + length;
|
||||||
|
}
|
||||||
|
|
||||||
|
const T* data() const {
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() const {
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
const T* ptr;
|
||||||
|
size_t length;
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user