126 lines
4.2 KiB
Lua
126 lines
4.2 KiB
Lua
local easing_lib = require "lib.utils.easing"
|
||
|
||
--- Обобщенная асинхронная функция (Task).
|
||
--- По сути это функция, принимающая коллбэк: `fun(callback: fun(value: T): nil): nil`
|
||
--- @generic T
|
||
--- @alias Task fun(callback: fun(value: T): nil): nil
|
||
|
||
local task = {}
|
||
local activeTweens = {}
|
||
|
||
--- Внутренний хелпер для интерполяции (числа или Vec3)
|
||
local function lerp(a, b, t)
|
||
if type(a) == "number" then
|
||
return a + (b - a) * t
|
||
elseif type(a) == "table" and getmetatable(a) then
|
||
-- Предполагаем, что это Vec3 или другой объект с операторами +, -, *
|
||
return a + (b - a) * t
|
||
end
|
||
return b
|
||
end
|
||
|
||
--- Обновление всех активных анимаций (твинов).
|
||
--- Нужно вызывать в love.update(dt)
|
||
function task.update(dt)
|
||
for i = #activeTweens, 1, -1 do
|
||
local t = activeTweens[i]
|
||
t.elapsed = t.elapsed + dt * 1000
|
||
local progress = math.min(t.elapsed / t.duration, 1)
|
||
local value = t.easing(progress)
|
||
|
||
for key, targetValue in pairs(t.properties) do
|
||
t.target[key] = lerp(t.initial[key], targetValue, value)
|
||
end
|
||
|
||
if progress >= 1 then
|
||
table.remove(activeTweens, i)
|
||
if t.resolve then t.resolve() end
|
||
end
|
||
end
|
||
end
|
||
|
||
--- Возвращает Completer — объект, который позволяет вручную завершить таску.
|
||
--- @return table completer { complete: fun(val: T) }, Task<T> future
|
||
function task.completer()
|
||
local c = { completed = false, value = nil, cb = nil }
|
||
function c:complete(val)
|
||
if self.completed then return end
|
||
self.completed = true
|
||
self.value = val
|
||
if self.cb then self.cb(val) end
|
||
end
|
||
|
||
local future = function(callback)
|
||
if c.completed then callback(c.value)
|
||
else c.cb = callback end
|
||
end
|
||
return c, future
|
||
end
|
||
|
||
--- Создает таску, которая плавно меняет свойства объекта.
|
||
--- @param target table Объект, свойства которого меняем
|
||
--- @param properties table Набор конечных значений { key = value }
|
||
--- @param duration number Длительность в мс
|
||
--- @param easing function? Функция смягчения (по умолчанию linear)
|
||
--- @return Task<nil>
|
||
function task.tween(target, properties, duration, easing)
|
||
local initial = {}
|
||
for k, _ in pairs(properties) do
|
||
initial[k] = target[k]
|
||
if type(initial[k]) == "table" and initial[k].copy then
|
||
initial[k] = initial[k]:copy() -- Для Vec3
|
||
end
|
||
end
|
||
|
||
local comp, future = task.completer()
|
||
table.insert(activeTweens, {
|
||
target = target,
|
||
initial = initial,
|
||
properties = properties,
|
||
duration = duration or 1000,
|
||
easing = easing or easing_lib.linear,
|
||
elapsed = 0,
|
||
resolve = function() comp:complete() end
|
||
})
|
||
return future
|
||
end
|
||
|
||
--- Возвращает таску, которая завершится сразу с переданным значением.
|
||
function task.fromValue(val)
|
||
return function(callback) callback(val) end
|
||
end
|
||
|
||
--- Возвращает новый Task, который завершится после завершения всех переданных `tasks`.
|
||
--- @param tasks Task[]
|
||
--- @return Task<any[]>
|
||
function task.wait(tasks)
|
||
if #tasks == 0 then return task.fromValue({}) end
|
||
local count = #tasks
|
||
local results = {}
|
||
|
||
return function(callback)
|
||
for i, t in ipairs(tasks) do
|
||
t(function(result)
|
||
results[i] = result
|
||
count = count - 1
|
||
if count == 0 then callback(results) end
|
||
end)
|
||
end
|
||
end
|
||
end
|
||
|
||
--- Последовательно объединяет два `Task` в один.
|
||
--- @param t Task
|
||
--- @param onCompleted fun(value: any): Task
|
||
--- @return Task
|
||
function task.chain(t, onCompleted)
|
||
return function(callback)
|
||
t(function(value)
|
||
local t2 = onCompleted(value)
|
||
t2(callback)
|
||
end)
|
||
end
|
||
end
|
||
|
||
return task
|