diff --git a/scripts/titlescreen/fields/boot/checkupdatefield.lua b/scripts/titlescreen/fields/boot/checkupdatefield.lua index e69de29..640c7fb 100644 --- a/scripts/titlescreen/fields/boot/checkupdatefield.lua +++ b/scripts/titlescreen/fields/boot/checkupdatefield.lua @@ -0,0 +1,66 @@ +require("common.class") +local Util = require("common.util") +local ServiceField = require("titlescreen.fields.service.servicefield") + +---@class CheckUpdateField: ServiceField +---@field onUpdateAvailable nil|fun(url: string, version: string) +---@field _timer number +local CheckUpdateField = { + __tostring = function() return "CheckUpdateField" end, + PROGRESS_FREQ = 1 / 5, -- 5Hz + CHECK_UPDATE_MOCK_DELAY = 3, -- seconds +} + +---Create a new CheckUpdateField instance +---@param o? table # initial parameters +---@return CheckUpdateField +function CheckUpdateField:new(o) + o = o or {} + + o._timer = o._timer or 0 + o.onUpdateAvailable = o.onUpdateAvailable or nil + + local this = Inherit(self, o, ServiceField) + + this._url = nil + this._version = nil + + return this +end + +function CheckUpdateField:drawLabel(deltaTime) + local text = self.label + local progress = math.ceil(Util.lerp(self._timer % self.PROGRESS_FREQ, + 0, 0, self.PROGRESS_FREQ, 4 + )) + text = text .. string.rep(".", progress) + + gfx.FontSize(self.FONT_SIZE) + gfx.LoadSkinFont(self.FONT_FACE) + gfx.TextAlign(gfx.TEXT_ALIGN_LEFT | gfx.TEXT_ALIGN_TOP) + gfx.FillColor(table.unpack(self.FONT_COLOR)) + gfx.Text(text, 0, 0) +end + +function CheckUpdateField:drawValue(deltaTime) + +end + +function CheckUpdateField:tick(deltaTime) + if self._timer > self.CHECK_UPDATE_MOCK_DELAY then + self._url, self._version = game.UpdateAvailable() + if self._url then + self.onUpdateAvailable(self._url, self._version) + else + self:getParentPage().viewHandler:clear() -- Exit out of bootscreen + end + end + self._timer = self._timer + deltaTime +end + +function CheckUpdateField:render(deltaTime) + self:tick(deltaTime) + ServiceField.render(self, deltaTime) +end + +return CheckUpdateField diff --git a/scripts/titlescreen/fields/boot/selftestfield.lua b/scripts/titlescreen/fields/boot/selftestfield.lua index 7a38175..c518ed2 100644 --- a/scripts/titlescreen/fields/boot/selftestfield.lua +++ b/scripts/titlescreen/fields/boot/selftestfield.lua @@ -11,16 +11,24 @@ SelfTestStatusEnum = { ERROR = 5 } +local function statusToString(status) + local statusName = {"IDLE", "INPROGRESS", "OK", "PASS", "ERROR"} + return statusName[status] +end + ---@class SelfTestField: ServiceField +---@field checkTask nil|fun(obj: any): SelfTestStatusEnum # a function that will run asynchronously on activating the Field ---@field status SelfTestStatusEnum ----@field callback nil|fun(obj: any): SelfTestStatusEnum +---@field onStatusChange nil|fun(status) # a callback function on finishing the checkTask +---@field _thread thread ---@field _timer number local SelfTestField = { - SELFTEST_COLOR_INPROGRESS = {255, 255, 255, 255}, - SELFTEST_COLOR_OK = {0, 255, 0, 255}, - SELFTEST_COLOR_PASS = {255, 255, 0, 255}, - SELFTEST_COLOR_ERROR = {255, 0, 0, 255}, - SELFTEST_INPROGRESS_FREQ = 1 / 20, --20Hz + __tostring = function () return "SelfTestField" end, + COLOR_INPROGRESS = {255, 255, 255, 255}, + COLOR_OK = {0, 255, 0, 255}, + COLOR_PASS = {255, 255, 0, 255}, + COLOR_ERROR = {255, 0, 0, 255}, + INPROGRESS_FREQ = 1 / 20, --20Hz } ---Create a new SelfTestField instance @@ -30,18 +38,61 @@ function SelfTestField:new(o) o = o or {} o.status = o.status or SelfTestStatusEnum.IDLE - o.callback = o.callback or nil o._timer = 0 + o._thread = nil + + assert((not o.onStatusChange) or (o.checkTask and o.onStatusChange), + "Failed to construct SelfTestField, checkTask is mandatory when onStatusChange is defined!\n" .. debug.traceback() + ) return Inherit(self, o, ServiceField) end -function SelfTestField:activate(obj) - if self.callback then - self.status = self.callback(obj) or SelfTestStatusEnum.INPROGRESS +function SelfTestField:_closeThread() + if self._thread and coroutine.status(self._thread) ~= "dead" then + coroutine.close(self._thread) end end +function SelfTestField:_resumeThread() + if self._thread and coroutine.status(self._thread) == "suspended" then + local success, status = coroutine.resume(self._thread) + game.Log(self.label .. ": success: " .. tostring(success) .. + ", status: " .. status .. " (" .. statusToString(status) .. ")", + game.LOGGER_DEBUG + ) + if success and status ~= self.status then + self.status = status + if self.onStatusChange then + game.Log("SKIN CONFIG: onStatusChange(" .. status .. ") (" .. + statusToString(status) .. ")", + game.LOGGER_DEBUG + ) + self.onStatusChange(status) + end + end + end +end + +function SelfTestField:activate(obj) + self:_closeThread() + + if self.checkTask then + self._thread = coroutine.create(self.checkTask) + self:_resumeThread() + end +end + +function SelfTestField:deactivate(obj) + self:_closeThread() +end + +function SelfTestField:tick(deltaTime) + self:_resumeThread() + + self._timer = self._timer + deltaTime +end + function SelfTestField:drawValue(deltaTime) gfx.Translate(self.VALUE_OFFSETX, 0) @@ -54,18 +105,19 @@ function SelfTestField:drawValue(deltaTime) color = self.FONT_COLOR text = "" elseif self.status == SelfTestStatusEnum.INPROGRESS then - self._timer = self._timer + deltaTime - local progress = math.ceil(Util.lerp(self._timer % 1, 0, 0, 1, 4)) - color = self.SELFTEST_COLOR_INPROGRESS + local progress = math.ceil(Util.lerp(self._timer % self.INPROGRESS_FREQ, + 0, 0, self.INPROGRESS_FREQ, 4 + )) + color = self.COLOR_INPROGRESS text = string.rep(".", progress) elseif self.status == SelfTestStatusEnum.OK then - color = self.SELFTEST_COLOR_OK + color = self.COLOR_OK text = "OK" elseif self.status == SelfTestStatusEnum.PASS then - color = self.SELFTEST_COLOR_PASS + color = self.COLOR_PASS text = "PASS" elseif self.status == SelfTestStatusEnum.ERROR then - color = self.SELFTEST_COLOR_ERROR + color = self.COLOR_ERROR text = "ERROR" end @@ -74,4 +126,9 @@ function SelfTestField:drawValue(deltaTime) gfx.Text(text, 0, 0) end +function SelfTestField:render(deltaTime) + self:tick(deltaTime) + ServiceField.render(self, deltaTime) +end + return SelfTestField \ No newline at end of file diff --git a/scripts/titlescreen/pages/boot/bootpage.lua b/scripts/titlescreen/pages/boot/bootpage.lua index aac6226..a311bb4 100644 --- a/scripts/titlescreen/pages/boot/bootpage.lua +++ b/scripts/titlescreen/pages/boot/bootpage.lua @@ -3,34 +3,11 @@ require("common.filereader") local Dim = require("common.dimensions") local Version = require("common.version") local Page = require("components.pager.page") +local CheckUpdatePage = require("titlescreen.pages.boot.checkupdatepage") local ServiceField = require("titlescreen.fields.service.servicefield") local ListField = require("titlescreen.fields.service.listfield") local SelfTestField = require("titlescreen.fields.boot.selftestfield") -local function checkSkinConfig(obj) - local crewpath = "textures/crew/anim/" .. game.GetSkinSetting("single_idol") - if not IsDir(crewpath) then - return SelfTestStatusEnum.ERROR - end -end - -local networkStatus = SelfTestStatusEnum.IDLE -local function irHeardbeat(res) - if res.statusCode == IRData.States.Success then - networkStatus = SelfTestStatusEnum.OK - else - networkStatus = SelfTestStatusEnum.ERROR - end -end - -local function checkNetwork(obj) - if not IRData.Active then - return SelfTestStatusEnum.PASS - end - - IR.Heartbeat(irHeardbeat) -end - ---@class BootPage: Page local BootPage = { __tostring = function() return "BootPage" end, @@ -44,14 +21,71 @@ function BootPage:new(o) local this = Inherit(self, o, Page) + this._networkResult = {} + this:addField(ServiceField:new{posX = 32, posY = 32, label = Version.getLongVersion(), value = ""}) this:addField(ServiceField:new{posX = 64, posY = 64, label = "UNNAMED SDVX CLONE STARTUP...", value = ""}) local valueOffX = 220 + this._mainIoTestField = SelfTestField:new{label = "MAIN I/O", VALUE_OFFSETX = valueOffX} + this._mainIoTestField.checkTask = function(obj) + return SelfTestStatusEnum.OK + end + this._mainIoTestField.onStatusChange = function(status) + if status == SelfTestStatusEnum.OK then + this._skinConfigTestField:activate() + end + end + + this._skinConfigTestField = SelfTestField:new{label = "SKIN CONFIG", VALUE_OFFSETX = valueOffX} + this._skinConfigTestField.checkTask = function(obj) + local crewpath = "skins/" .. game.GetSkin() .. "/textures/crew/anim/" .. game.GetSkinSetting("single_idol") + if not IsDir(crewpath) then + return SelfTestStatusEnum.ERROR + end + return SelfTestStatusEnum.OK + end + this._skinConfigTestField.onStatusChange = function(status) + if status == SelfTestStatusEnum.OK then + this._networkTestField:activate() + end + end + + this._networkTestField = SelfTestField:new{label = "NETWORK", VALUE_OFFSETX = valueOffX} + -- set up async network check + this._networkTestField.checkTask = function(obj) + local status = SelfTestStatusEnum.INPROGRESS + + if not IRData.Active then + return SelfTestStatusEnum.PASS + end + + while status == SelfTestStatusEnum.INPROGRESS do + if this._networkResult.statusCode == IRData.States.Success then + status = SelfTestStatusEnum.OK + elseif this._networkResult.statusCode then + status = SelfTestStatusEnum.ERROR -- there's a response, but it's not success + end + + coroutine.yield(status) + end + + return status + end + this._networkTestField.onStatusChange = function(status) + if status == SelfTestStatusEnum.INPROGRESS then + IR.Heartbeat(function(res) this._networkResult = res end) -- IR doesn't like being called in a coroutine + elseif status == SelfTestStatusEnum.PASS or status == SelfTestStatusEnum.OK then + if this.viewHandler then + this.viewHandler:navigate(CheckUpdatePage:new()) + end + end + end + local list = ListField:new{posX = 64, posY = 96} - list:addField(SelfTestField:new{label = "MAIN I/O", VALUE_OFFSETX = valueOffX, status = SelfTestStatusEnum.OK}) - list:addField(SelfTestField:new{label = "SKIN CONFIG", VALUE_OFFSETX = valueOffX, status = SelfTestStatusEnum.OK, callback = checkSkinConfig}) - list:addField(SelfTestField:new{label = "IR NETWORK", VALUE_OFFSETX = valueOffX, status = SelfTestStatusEnum.ERROR, callback = checkNetwork}) + list:addField(this._mainIoTestField) + list:addField(this._skinConfigTestField) + list:addField(this._networkTestField) this:addField(list) return this @@ -65,12 +99,13 @@ function BootPage:drawBackground(deltaTime) gfx.Fill() end +local first = true function BootPage:render(deltaTime) + if first then + self._mainIoTestField:activate() + first = false + end Page.render(self, deltaTime) - - local skinconfig = self.content[3].content[2] --this is hacktastic - - local network = self.content[3].content[3] --this is also hacktastic end return BootPage diff --git a/scripts/titlescreen/pages/boot/checkupdatepage.lua b/scripts/titlescreen/pages/boot/checkupdatepage.lua new file mode 100644 index 0000000..13b53ea --- /dev/null +++ b/scripts/titlescreen/pages/boot/checkupdatepage.lua @@ -0,0 +1,34 @@ +require("common.class") +local Dim = require("common.dimensions") +local Page = require("components.pager.page") +local CheckUpdateField = require("titlescreen.fields.boot.checkupdatefield") + +---@class CheckUpdatePage: Page +local CheckUpdatePage = { + __tostring = function() return "CheckUpdatePage" end, +} + +---Create a new CheckUpdatePage instance +---@param o? table # initial parameters +---@return CheckUpdatePage +function CheckUpdatePage:new(o) + local this = Inherit(self, o, Page) + + this._checkUpdateField = CheckUpdateField:new{posX = 32, posY = 64, label = "update check"} + this._checkUpdateField.onUpdateAvailable = function(url, version) + + end + this:addField(this._checkUpdateField) + + return this +end + +---@param deltaTime number # frametime in seconds +function CheckUpdatePage:drawBackground(deltaTime) + gfx.BeginPath() + gfx.FillColor(0, 0, 0) + gfx.Rect(0, 0, Dim.design.width, Dim.design.height) + gfx.Fill() +end + +return CheckUpdatePage