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 = {} -- list of tweens -- We also need a way to track tweens by target to support cancellation/replacement -- Let's stick to a simple list for update, but maybe add a helper to find by target? -- Or, just allow multiple tweens per target (which is valid for different properties). -- But if we animate the SAME property, we have a conflict. -- For now, let's just add task.cancel(target) which kills ALL tweens for that target. --- Внутренний хелпер для интерполяции (числа или 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] if not t.cancelled then 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 t.completed = true table.remove(activeTweens, i) if t.resolve then t.resolve() end end else table.remove(activeTweens, i) end end end --- Возвращает Completer — объект, который позволяет вручную завершить таску. --- @generic T --- @return { complete: fun(val: T) }, Task 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 function task.cancel(target) for _, t in ipairs(activeTweens) do if t.target == target then t.cancelled = true end end end --- Создает таску, которая плавно меняет свойства объекта. --- Автоматически отменяет предыдущие твины для этого объекта (чтобы не было конфликтов). --- @param target table Объект, свойства которого меняем --- @param properties table Набор конечных значений { key = value } --- @param duration number Длительность в мс --- @param easing function? Функция смягчения (по умолчанию linear) --- @return Task function task.tween(target, properties, duration, easing) task.cancel(target) -- Cancel previous animations on this target 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 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