engine = engine
  or {
    resized = 0,
    focused = true,
    devMode = true,
    fileDropped = false,
    renderSelection = false,

    files = {},
    contexts = {},
    globals = {},
    symbols = {},
    splash = {},
    keysDown = {},
    sourceModTimes = {},
    allResolutions = {},
    canvases = {},
    texmem = 0,

    frameTimePeriod = 30,
    trueTimes = {},
    frameTimes = {},
    frameGarbages = {},
    syncedFrameTime=0,
    lastFrameTime=0,
    frameTime = 0,
    trueTime=0,

    trace = {
      x = 0,
      hovered=0,
      enabled = true,
      height = 0,
      limit = 37,
      width = 280,
      strings = {},
      funcs = {},
    },

    defaultLevel = {
      key = "",
      name = "Untitled Level",
      variables = {},
      defaultPage = "defaultPage",
      connections = {},
      pages = {
        defaultPage = {
          key = "defaultPage",
          name = "Untitled Page",
          dialogue = {},
          views = { { key = "defaultRoom", cameraID = 1, vieportID = 1, depth = 1 } },
          sky = {
            image = {
              key = "",
              frame = 1,
              play = false,
              color = { r = 1, g = 1, b = 1, a = 1 },
            },
            color = { r = 0, g = 0, b = 0, a = 1 },
          },
        },
      },
      rooms = {
        "defaultRoom",
      },
    },

    defaultRoom = {
      ox = 0,
      oy = 0,
      key = "defaultRoom",
      name = "Untitled Room",
      isRoom=true,
      dialogue = {},
      sky = {
        image = {
          key = "",
          frame = 1,
          play = false,
          color = { r = 1, g = 1, b = 1, a = 1 },
        },
        color = { r = 0.8, g = 0.8, b = 0.8, a = 1 },
      },
      transform = { x = 0, y = 0, s = 1 },
      cameras = {},
      viewports = {
        {
          polys = { { 0.2, 0.2, 0.8, 0.2, 0.8, 0.8, 0.2, 0.8 } },
          center = { x = 0.5, y = 0.5 },
          wobble = {
            speed = 0,
            dist = 0,
          },
        },
      },
      instances = {},
      paths = {},
      ramps = {},
      floors = {},
      scripts = {},
      variables = {},
      visible = true,
      zbounds = {
        back = 0,
        front = 1000,
        range = 1000,
      },
    },
  }
  mouse = mouse or {}
local msaaTest
local testMesh
local fanMesh

local function getFileID(path)
  if lfs.isDir(path,true) then
    local id
    for i,item  in ipairs(lfs.getDirectoryItems(path,true) ) do
      if not item.name then return end
      if item.name:sub(1, 2) == ".u" then
        id = item.name:sub(2)
        break
      end
    end
    -- print("ID",id)
    local dir = path .. "/." .. id
    -- print("DIR: ",dir)
    if lfs.exists(dir,true) then
      -- print(dir)
      local file = io.open(dir, "r+")
      if file then
        -- print("file",file)
        local str = file:read("a")
        local fileConfig = loadstring(str)()
        -- print("ID is ", fileConfig.id)
        file:close()
        return fileConfig.id
      end
    end
  else
    local file = io.open(path, "r+")
    if file then
      local len = file:seek("end")
      file:seek("set", len - 32)
      local text = file:read("*a")
      local id = string.match(text, "[^%c]*$")
      file:close()
      return id
    end
  end
end

local function clearProjectFolder()
  for i,item in ipairs(lfs.getDirectoryItems(SAVE_DIR .. "projects",true) ) do
    if i and item.name then lfs.remove(item.path, true) end
  end
end

local function clearTempProject()
  if ctx and ctx.level and ctx.level.temp then
    print("EVEL TEMP//////////////////////")
    local path = config.projects[engine.project].dir
    local name = config.projects[engine.project].name
    local ext = config.projects[engine.project].fileType
    local dest = (path .. "/" .. name .. ext):gsub("//", "/")
    print(dest)
    os.remove(dest)
    clearProjectFolder()
  end
end

local function nextUntitled(t)
  local nextUntitled = 0
  if t then
    for k, v in pairs(t) do
      if type(v) == "table" then
        local n = tonumber(v.name:sub(10))
        if v.name:sub(1, 9) == "Untitled " and type(n) == "number" and n > nextUntitled then nextUntitled = n end
      end
    end
  else
    return math.floor(math.random() * 1000)
  end

  return nextUntitled + 1
end

local function newProject(anim)
  local name = "Untitled " .. nextUntitled(config.projects)
  local id = uuid()
  local ext = (anim and ".umou" or ".project")
  local dir = anim and "projects/" .. id or config.defaultProjectDirectory .. name .. ext

  config.projects[id] = {
    id = id,
    name = name,
    dir = config.defaultProjectDirectory,
    modtime = os.time(),
    isAnim = anim,
    fileType = ext,
  }

  print("new project",inspect(config.projects[id]))

  ZIP_MODE = anim

  lfs.mkdir(dir)
  lfs.mkdir(dir, "rooms")
  lfs.mkdir(dir, "symbols")
  lfs.mkdir(dir, "assets")
  lfs.mkdir(dir, "scripts")

  local dlevel = lfs.wrapLua(inspect({ levels = { defaultLevel = deepcopy(engine.defaultLevel) } }))
  local droom = lfs.wrapLua(inspect(deepcopy(engine.defaultRoom)))

  if ZIP_MODE then
    lfs.write(dlevel,dir.."/".. "." .. id)
    lfs.write(droom,dir.."/".."rooms".."/defaultRoom")
  else
    lfs.write(dlevel, {"." .. id},true)
    lfs.write(droom, {"rooms", "defaultRoom"},true)
    os.link(dir, SAVE_DIR .. "projects")
  end


  load_saveConfig()
  engine.splash.refresh = true
  engine_loadProject(id, true)

  return id
end

local function addProject(dir)
  local id = getFileID(dir)
  if config.projects[id] or not id then return end
  print("Add Project:")
  print(string.format(" %s\n %s\n %s\n %s\n", dir, basename(dir), dir:gsub(basename(dir), ""), id))
  local bdir = basename(dir)
  print("bdir:sub(bdir:len()-4,-1)", bdir:sub(bdir:len() - 4, -1))
  local name = bdir:gsub(".project", ""):gsub(".umou", "")
  config.projects[id] = {
    id = id,
    name = name,
    dir = dir:gsub(bdir, ""),
    modtime = os.time(),
    fileType = bdir:sub(bdir:len() - 4, -1) == ".project" and ".project" or ".umou",
  }
  if not lfs.isFile(dir, true) then
    os.link(dir, SAVE_DIR .. "projects")
    os.rename(SAVE_DIR .. "projects/" .. name, SAVE_DIR .. "projects/" .. id)
  end
  engine.splash.refresh = true
  load_saveConfig()
end

local function openProject()
  local file = args[1]
  local base = basename(file)
  local LOADED_FILE = file
  local LOADED_NAME = base:gsub(".umou", "")
  local LOADED_PATH = file:gsub(base, "")

  local id = getFileID(file)
  local LOADED_ID = id
  lfs.write("id", id)

  config.projects[id] = {
    id = id,
    dir = LOADED_PATH,
    name = LOADED_NAME,
    modtime = os.time(),
    fileType = ".umou",
  }
  lfs.mkdir("projects")

  engine_loadProject(id, false, true)

  args[1] = nil
end

local function setResolution(alreadySet)
  if #engine.contexts > 1 then return end
  print("setResolution", loadedWidth, loadedHeight)
  local w, h = screenW, screenH
  -- print("w,h",w,h)
  lwindow.setMode(loadedWidth, loadedHeight, {
    minwidth = 480,
    minheight = 480,
    resizable = engine.devMode,
    fullscreen = false,
    vsync = true,
    display = config.window.display,
    borderless = false,
    fullscreentype = "desktop",
  })

  lwindow.setPosition(config.window.position.x, config.window.position.y, config.window.display)
  engine_setDimensions()
end

local function setLaunchRes()
  print("SET LAUNCH RES")
  lwindow.setTitle("Umoupen")
  local w, h = screenW, screenH
  lwindow.setMode(launchWidth, launchHeight, {
    fullscreen = false,
    vsync = true,
    minwidth = launchWidth,
    minheight = launchHeight,
    resizable = engine.devMode,
    display = config.window.display,
  })
  local x, y, d = lwindow.getPosition()
  print(x, y)
  print(w)
  print(x - (launchWidth - w) / 2, y - (launchHeight - h) / 2)
  lwindow.setPosition(x - (launchWidth - w) / 2, y - (launchHeight - h) / 2, config.window.display or d)
  -- lwindow.setPosition(
  --   config.window.position.x + (config.window.size.width - launchWidth) / 2,
  --   config.window.position.y + (config.window.size.height - launchHeight) / 2,
  --   config.window.display
  -- )
end

local function initDisplay()
  if not engine.release then return end
  local hdfound = 0
  local preResolutions = { { width = 1280, height = 720 } }

  table.sort(preResolutions, function(a, b)
    return a.width * a.height < b.width * b.height
  end)

  -- CHECK FOR SUPPORTED RESOLUTIONS --
  for i, v in ipairs(preResolutions) do
    if v.height < math.round((v.width / 16) * 11) and v.height > math.round((v.width / 16) * 7) then
      v.name = v.width .. " x " .. v.height
      v.func = function(x)
        config.fullScreenRes = x
        config.window.size = engine.allResolutions[config.fullScreenRes]
      end
      table.insert(engine.allResolutions, v)

      if v.height == 720 and v.width == 1280 then hdfound = #engine.allResolutions end
    end
  end

  -- LAUNCH IN 720 BY DEFAULT IF AVAILABLE
  if config.fullScreenRes == nil then
    if hdfound > 0 then
      config.fullScreenRes = hdfound
    else
      config.fullScreenRes = #engine.allResolutions
    end
  end

  config.window.size = config.window.size or engine.allResolutions[config.fullScreenRes]

  setLaunchRes()
end

function engine_setPanels()
  if not ctx then return end
  timeline_setPanel(ctx.animatePanel)
  gallery_setPanel(ctx.galleryPanel)
  plan_setPanel(ctx.planPanel)
  paths_setPanel(ctx.pathsPanel)
end
local function switchContext(tab)
  engine.currentTab = tab
  engine.tabSet = tab
  if not engine.contexts[tab] then return end

  print(ansicolors(" %{yellowbg}Switch Context:%{reset}"), tab)

  ctx = engine.contexts[tab]
  engine.project = ctx.project

  setmetatable(_G, {
    __index = engine.contexts[tab],
    __newindex = _LIVE and function(table, key, value)
      if key=="room" then
        error("NO")
      end
      local info = debug.getinfo(2, "Sl")
      if not rawget(_ASSIGNS, key) then rawset(_ASSIGNS, key, info) end
      rawset(table, key, value)
    end or nil,
  })

  engine_setPanels()
  load_setSymbols(ctx.symbols)
  -- setmetatable(_G , {__index= function(t,k)
  --   return rawget(ctx,k) or rawget(t,k)
  -- end})
end

local function resizeWindow(w, h, skipConfig)

  if engine.window.resized then
    lwindow.getPosition()
    if editor then
      editor.resized = 2
      timeline_refresh()
      game.aspectRatio = gfx.getWidth() / gfx.getHeight()
      animatePanel.hideSpeed = 1
    end

    engine_setDimensions()

    if engine.project then engine_resizeCanvas() end

    if skipConfig then return end

    if w then
      load_saveConfig()
    end
    engine.window.resized = false
  end

end

local function splashUpdate()
  local splash = engine.splash
  mouse_set(nothing)
  engine_tabKeys()
  config.projects = config.projects or {}
  local numProjects = lume.count(config.projects)

  local projStr = ""
  for p, project in pairs(config.projects) do
    if project.id then
      projStr = projStr .. project.id
    end
  end
  -- splash.projStr ~= projStr
  splash.previewSize = 70
  local previewSize = 70
  local splashLeft = 40

  if splash.refresh or not splash.items then
    splash.items = {}
    update = function(item, i)
      if item.thumb then
        local tw, th = item.thumb:getWidth(), item.thumb:getHeight()
        local aspect = tw / th
        item.w, item.h = previewSize * aspect, previewSize
        item.sw, item.sh = item.w / tw, item.h / th
      end
      if i > 1 and splash.items[i - 1].id and not item.id then
        splash.x = 10
        splash.y = screenH - 50
      end
      if item.opt then
        item.x = (screenW - item.bx)
        item.y = (screenH - item.by)
        item.highlight = item.mode == config.loadMode
      elseif item.size then
        item.w = string.len(item.text) * 6 + 12

        item.x = splash.x
        item.y = splash.y
        splash.x = splash.x + item.w + 10

        if item.last then splash.y = splash.y + 36 end
      else
        -- item.w = mf(200+(item.new and -80  or 0))
        if splash.x + item.w + 10 > screenW - 20 then
          splash.x = splashLeft
          splash.y = splash.y + item.h + 20
        end
        item.x = splash.x
        item.y = splash.y
        splash.x = splash.x + item.w + 10
      end
      item.h = item.h or 20
      item.hovered = item.click and (not item.id or (mouse.y < splash.portyh)) and mouse_in(item)
      item.a = item.a or 0
      item.a = item.a + ((item.hovered and 1 or 0) - item.a) / 4
      if item.hovered then
        -- mouse_setCursor(cursors.hand)
        mouse_set(function()
          item.click(item)
        end)
      end
    end
    draw = function(item)
      if not item.dir then gfx.setScissor() end
      gfx.push()
      gfx.setLineWidth(2)
      gfx.fontSize(item.dir and 11 or 13)
      gfx.translate(item.x, item.y)
      gfx.setColor(0, 0, 0, item.a * 0.5)
      gfx.rectangle("fill", -4, -2, item.w, item.h, 2)
      if item.click then
        gfx.setColor(1, 1, 1, 0.1)
        gfx.rectangle("fill", -4, -2, item.w, item.h, 2)
      end
      gfx.setColor(1, 1, 1, (item.hovered or item.static) and 1 or 0.7)
      if item.highlight then
        gfx.setLineWidth(2)
        gfx.setColor(1, 1, 1, 1)
        gfx.rectangle("line", -4, -2, item.w, item.h, 2)
      end
      if item.mode or item.center then
        gfx.printf(item.text or item.name, item.size and -2 or 0, item.dir and item.h + 2 or 1.5, item.w * 0.9, "center")
      else
        gfx.print(item.text or item.name,  1, item.dir and item.h + 2 or 1.5)
      end
      if item.dir then
        gfx.fontSize(9)
        gfx.setColor(1, 1, 1, 0.5)

        if item.thumb then
          gfx.setColor(1, 1, 1, 1)
          gfx.draw(item.thumb, -4, -2, 0, item.sw, item.sh)
          gfx.setColor(0.5, 0.5, 0.5, 1)
          gfx.rectangle("line", -4, -2, item.w, item.h, 2)
          if item.hovered then
            gfx.setLineWidth(3)
            gfx.setColor(1, 1, 1, 1)
            gfx.rectangle("line", -4, -2, item.w, item.h, 2)

            splash.itemHovered = item
            -- gfx.draw(item.thumb,item.w+8)
            -- gfx.rectangle("line",item.w+8,0,256,item.thumb:getHeight(),8)
            -- gfx.setLineWidth(2)
          end
        end
      end
      gfx.pop()
    end

    if numProjects > 0 then
      splash.projStr = projStr
      print("\n" .. numProjects .. " projects found :")
      for p, project in pairs(config.projects) do
        if project.dir and project.fileType then
          local proj = (project.dir .. "/" .. project.name .. project.fileType):gsub("//", "/")
          print("  --> " .. proj)
          if exists(proj) then
            print("    -> exists!")
          else
            local inContext = false
            for k,con in pairs(engine.contexts) do 
              print("  :", con.project,project.id)
              if con.project == project.id then 
                inContext=true
              end
            end
            if inContext then 
              print("    -> unsaved!")
            else
              print("    -> nilled! " .. proj)
              config.projects[p] = nil
            end
          end
        else
          config.projects[p] = nil
        end
        print("")
      end

      for p, project in pairs(config.projects) do
        -- print("project.modtime",project.modtime)
        local w, h
        local thumb
        local sw, sh
        if project.dir then
          local path = "thumbnails/" .. tostring(project.id) .. ".png"
          if lfs.exists(path) then
            thumb = gfx.newImage(SAVE_DIR .. "thumbnails/" .. tostring(project.id) .. ".png")
            thumb:setFilter("linear", "linear")
          end
        end
        table.insert(splash.items, {
          name = project.name,
          id = project.id,
          update = update,
          dir = project.dir,
          thumb = thumb,
          w = w or 100,
          h = h or previewSize,
          draw = draw,
          time = project.modtime,
          click = function(item)
            engine_loadProject(item.id)
          end,
        })
      end
      table.sort(splash.items, function(a, b)
        if a.time == b.time then return a.name < b.name end
        return a.time > b.time
      end)
    end

    splash.sizes = splash.sizes
      or {
        { name = "game" },
        { width = 1920, height = 1080, name = "1080p" },
        { width = 3840, height = 2160, name = "4K" },
      }

    table.insert(splash.items, {
      text = "new:",
      w = 30,
      static = true,
      update = update,
      draw = draw,
    })



    for s, size in ipairs(splash.sizes) do
      table.insert(splash.items, {
        new = true,
        size = size,
        text = size.name,
        update = update,
        center = true,
        last = s == #splash.sizes,
        draw = draw,
        click = function(item)
          splash.sizeSelected = size
          local id = newProject(size.name ~= "game")
        end,
      })
      if s==1 then
        table.insert(splash.items, {
      text = "animation:",
      w = 60,
      x=500,
      static = true,
      update = update,
      draw = draw,
    })
      end
    end

    --..(config.autoload and "On" or "Off")
    splash.optWidth = 4
    local modes = { "splash", "blank", "last" }
    for m, mode in lume.ripairs({ "on open:", "splash screen", "blank file", "last file" }) do
      local w = string.len(mode) * 6 + 10
      splash.optWidth = splash.optWidth + w + 8
      table.insert(splash.items, {
        opt = true,
        by = 50,
        static = m == 1,
        mode = modes[m - 1],
        bx = splash.optWidth,
        w = w,
        text = mode,
        update = update,
        draw = draw,
        click = m > 1 and function(item)
          config.loadMode = modes[m - 1]
          load_saveConfig()
          splash.refresh = true
        end or nil,
      })
    end

    splash.refresh = false
  end


  splash.tabH = #engine.contexts > 0 and 24 or 0
  if lume.count(config.projects) > 0 then
    splash.y = 60 + splash.tabH
    splash.x = splashLeft
  else
    splash.x = 10
    splash.y = screenH - 50
  end
  splash.porty = 30
  splash.portyh = screenH - 60
  splash.porty2 = screenH - splash.porty - 60
  splash.itemHovered = nil
  for i, item in ipairs(splash.items) do
    item:update(i)
  end
  gfx.setFont(fonts.interfont, 13)
  for i,item in ipairs(lfs.getDirectoryItems(config.defaultProjectDirectory,true) ) do
    local name = item.name
    if name and (name:sub(#name - 4) == ".project" or name:sub(#name - 4) == ".umou") then addProject(item.path) end
  end

  clearProjectFolder()

  load_saveConfig()
end

local function splashDraw()
  local splash = engine.splash
  engine.halfTime = engine_getTime()-engine.startTime

  gfx.clear(0.1, 0.1, 0.18, 1)

  gfx.beginPass(true)

  engine.lastTime = engine_getTime()

  gfx.setColor(1, 1, 1, 1)

  -- testFanStrip()
  -- gfx.draw(_TEST_MESH)

  gfx.push("all")
  gfx.fontSize(20)
  gfx.push()
  gfx.setColor(1, 1, 1, 1)
  

  gfx.print(lume.count(config.projects) == 0 and "looks like you don't have any recent files" or "recent files", 20, 20 + splash.tabH)
  gfx.setScissor()

  gfx.pop()
  gfx.fontSize(14)

  gfx.setColor(1, 1, 1, 0.6)
  gfx.print(" drag a folder into the window to set the default folder", screenW - 300, 15 + splash.tabH)
  gfx.print("             or drag an umou file into the window to add it", screenW - 300, 30 + splash.tabH)

  gfx.setLineWidth(2)

  gfx.setScissor(0, splash.porty + splash.tabH, screenW, splash.porty2 - splash.tabH)
  -- gfx.setScissor(10,10,20,20)
  if splash.items then
    for i, item in ipairs(splash.items) do
      item:draw()
    end
  end

  if splash.itemHovered then
    local item = splash.itemHovered
    local path = (item.dir .. "/" .. item.name):gsub("//", "/")
    -- path=path..path
    gfx.fontSize(11)
    gfx.push()
    gfx.translate(item.x, item.y)
    gfx.setColor(1, 1, 1, 1)
    gfx.rectangle("fill", -5, item.h - 12, string.len(path) * 5 + 5, 10, 2)
    gfx.setColor(0, 0, 0, 1)
    gfx.print(path, -5, item.h - 12)
    gfx.pop()
  end

  -- if not lfs.isFused() then
  --   if  args then
  --    gfx.print(tostring(0).." "..args[0]:sub(1,#args[0]-(WINDOWS and 11 or (lfs.isFused() and 6 or 0))),300,10)
  --    for a,arg in ipairs(args) do
  --      gfx.print(tostring(a).." "..arg,300,a*20+10)
  --    end
  --   end
  -- end

  -- if lfs.isFused() then
  --  if  OPENED_FILES then
  --    for a,arg in ipairs(OPENED_FILES) do
  --      gfx.print(tostring(a).." "..arg,310,a*20+200)
  --    end
  --  end
  -- end

  local size = 12
  gfx.fontSize(size)
  gfx.setColor(0, 0, 0, 0.3)
  gfx.rectangle("fill", 0, gfx.getHeight() - 17, gfx.getWidth(), 20)
  gfx.setColor(1, 1, 1, 0.5)

  gfx.print("default project folder: " .. config.defaultProjectDirectory, 4, gfx.getHeight() - 14)
  gfx.fontSize(10)
  gfx.pop()
end

local function openFile()
  namer_warning("Save Before Switching Files", {
    {
      name = "Save",
      color = colors.green,
      func = function()
        save_animation(animatePanel)
        engine_exit()
      end,
    },
    {
      name = "Don't Save",
      color = colors.red,
      func = function()
        engine_exit()
      end,
    },
    {
      name = "Cancel",
      func = function()
        namer_close()
        args[1] = nil
      end,
    },
  })
end

local function openFiles(boot)
  -- print("open files")
  -- no mo g lfw
  -- if ffi.os == "OSX" then
  --   local fileCount = tonumber(getOpenedFileCount())
  --   if fileCount and fileCount > 0 then
  --     _LOG("opened files----------", fileCount)
  --     OPENED_FILES = {}
  --     local ptr = getOpenedFilenames()
  --     ptr = ffi.cast("char**", ptr)
  --     for i = 0, fileCount - 1, 1 do
  --       local path = ffi_string(ptr[i])
  --       _LOG(" > ", path)
  --       table.insert(OPENED_FILES, path)
  --       if path:sub(path:len() - 4) == ".umou" then
  --         args[1] = path
  --         if not boot then
  --           if engine.project then
  --             openFile()
  --           else
  --             openProject()
  --           end
  --         end
  --       end
  --     end
  --     _LOG("----------------------")
  --   end
  --   resetOpenedFileCount()
  -- end
  -- print("open files2")
end

local function directoryDropped(dir)
  if not engine.project then
    print(dir)
    -- if dir:sub(dir:len()-4,-1)==".project" then
    --   engine_addProject(dir)
    -- else
    --   config.defaultProjectDirectory = dir
    -- end
  end
end

local function levelExists(name)
  return lume.any(ctx.levels, function(x)
    return x.name == name
  end)
end

local function pollWaits()
  for w, wait in lume.ripairs(game.waiting) do
    if wait() then table.remove(game.waiting, w) end
  end
end


local function run()
  if not args[1] then openFiles(true) end

  local forceSplash = engine.setRestart
  engine.setRestart = false
  print("args", inspect(args))

  SAVE_DIR = (lfs.getSaveDirectory() .. "/"):gsub("//", "/")
  ENGINE_DIR = lfs.getSource() .. "/"

  local m = 0.4

  colors = {
    white = { 1, 1, 1, r = 1, g = 1, b = 1 },
    black = { 0, 0, 0 },
    green = { r = 0.45 * m, g = 1 * m, b = 0.55 * m, a = 1 },
    red = { r = 1 * m, g = 0.4 * m, b = 0.4 * m, a = 1 },
  }

  mouse = { x = 0, y = 0, px = 0, py = 0 }
  keyboard = {}
  bench = {
    on = true,
    enabled = true,
    period = 20,
    step = 1,
    keys = {},
    folded = {},
    lines = {},
    source = {},
    times = {},
    trash = {},
    count = {},
    meanTimes = {},
    meanTrash = {},
    meanCount = {},
    startGarbage = {},
    startTimes = {},
    names = {},
    hasChildren = {},
    push = nothing,
    pop = nothing
  }

  engine_loadSource()
  mouse_define()

  gfx.setup()

  renderW = launchWidth
  renderH = launchHeight
  screenW = launchWidth
  screenH = launchHeight

  for k, dir in pairs({ "symbols", "saves", "cache", "thumbnails", "projects" }) do
    lfs.createDirectory(dir)
  end

  load_loadConfig()
  engine_resizeCanvas()
  initDisplay()

  RectMesh = RectMesh
    or gfx.newMesh(zformat, {
      { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 },
      { 1, 0, 0, 0, 1, 0, 1, 1, 1, 1 },
      { 1, 1, 0, 0, 1, 1, 1, 1, 1, 1 },

      { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 },
      { 1, 1, 0, 0, 1, 1, 1, 1, 1, 1 },
      { 0, 1, 0, 0, 0, 1, 1, 1, 1, 1 },
    }, "triangles")

  -- LOAD UI
  print("Loading UI...")
  interface_images = 0
  for n, name in ipairs(_internal_interface_image_list) do
    -- print("load ui",n,name)
    load_loadSymbol(name, "interface")
    interface_images = interface_images + 1
  end
  print("   UI Loaded")

  clearProjectFolder()
  splashUpdate()

  _time("prep")

  print("initialized")

  if lfs.isFused() and args and args[1] then
    openProject()
    loadFromConfig = false
  elseif forceSplash then
    print("Force Splash Screen")
    loadFromConfig = false
  else
    print("Check Projects, mode:", config.loadMode)
    if config.loadMode == "blank" then
      engine.splash.sizeSelected = { width = "1920", height = "1080" }
      local id = newProject(true)
      print(" -> loading new blank file")
    elseif config.loadMode == "last" then
      print("config,engine.project",config , config.project)
      if config.project then
        print("Is there a current project?")
        if config.projects[config.project] then
          print("Seems so")
          if lfs.fileExists("config.lua") or (not lfs.isFused()) then
            print("...lets load it")
            engine_loadProject(config.project)
            _time("load project")
          end
        else
          engine.project = nil
          print("NIL BROJEC")
        end
      end
    end
  end

  print(" -> No Current Project, Load Splash Screen")
  print("")

  if ffi.os == "Windows" then
    if config.windowsExePath ~= args[0] then
      config.windowsExePath = args[0]
      _LOG("update windows file assoc", args[0])
      os.execute("assoc .umou=" .. args[0])
    end
  end

  collectgarbage("restart")
  engine.resized = 0
  local msaaTest = newCanvas(1, 1, { msaa = 8 })
  print("msaa disabled: ", gfx.disableMSAA)
end

local function frame()

  gfx.newFrame()
  time = time + engine.dt
   if gfx.getResized() then resizeWindow(gfx.getWidth(), gfx.getHeight()) end
  engine.fourstep = (engine.fourstep and engine.fourstep < 3) and engine.fourstep + 1 or 1
  mouse.time = (mouse.time or 0) + 1
  input_updateControls()
  resizeWindow()
  gfx.setCanvas(engine.canvas)
  -- for k,v in pairs(config.projects) do 
  --   print(k,v.name)
  -- end
  -- HANDLE CONTEXTS
  if engine.closedTab then
    engine.splash.refresh = true
    engine.closedTab = false

    local hasThumb
    for i, item in ipairs(engine.splash.items) do
      if item.id == ctx.project then
        if item.thumb then
          hasThumb = true
          break
        end
      end
    end
    print("hasThumb", hasThumb)
    if not hasThumb then save_thumbnail() end

    game_exit()

    for k,v in pairs(engine.contexts[engine.currentTab]) do
      engine.contexts[engine.currentTab][k] = nil
    end
    table.remove(engine.contexts, engine.currentTab)
    collectgarbage("collect")

    print("#engine.contexts", #engine.contexts)
    if #engine.contexts == 0 then
      engine.splash.refresh = true
      engine.tabSet = 0
      engine.currentTab = 0
      engine.contexts = {}
      setmetatable(_G, {})
      mouse = {
        x=0,y=0,
        px=0,py=0
      }
      mouse_define()
      print("engine.contexts", inspect(engine.contexts))
      engine.project = nil
      print("engine.project1", engine.project)

      setLaunchRes()
    else
      engine.tabSet = math.max(1, engine.currentTab - 1)
      switchContext(engine.tabSet)
    end
    resizeWindow(0, 0, true)
  elseif engine.project and engine.callNew then
    engine.splash.refresh = true
    engine.prevTab = engine.currentTab
    engine.tabSet = #engine.contexts + 1
    engine.project = nil
    engine.callNew = false
    print("NEW TAB????")
  elseif engine.tabSet ~= engine.currentTab then
    switchContext(engine.tabSet)
  end




  -- TODO handle dropped/opened files
  openFiles()
  if lfs.IsFileDropped() then
    local fileCount = ffi.new("int[1]")
    local files = ffi.cast("char**", lfs.GetDroppedFiles(fileCount))
    for i = 0, fileCount[0] - 1, 1 do
      local path = ffi_string(files[i])
      if ctx and ctx.editing then
        engine.fileDropped = true
        droppedFile = file
        editor_dropFile(file)
      else
        if not engine.project then
          local dir = type(file) == "string" and file or file:getFilename()

          if dir:sub(dir:len() - 4) == ".umou" or dir:sub(dir:len() - 4) == ".project" then
            addProject(dir)
            engine.splash.refresh = true

            load_saveConfig()
          else
            config.defaultProjectDirectory = dir
          end
        end
      end
    end
    lfs.ClearDroppedFiles()
  end




  --TODO afx
  -- afx.update(engine.dt)




  -- UPDATE
  startTimeFrame()
  traceReset()
  if engine.project then

    bench.run(false)
    bench.push("update")

    if cmdDown and input_keyPressed("f") then
      lwindow.setFullscreen(not lwindow.getFullscreen())
    end

    gfx.setDefaultFilter("nearest", "nearest")

    mouse_set(nothing)
    pollWaits()

    scene_toggleResolution()

    if game.state == "Load" then
      engine_loadGame()
    elseif game.state == "Game" then
      -- if not game.paused then
        game_update()
        scene_updateScene()
      -- end
    elseif game.state == "Edit" then
      bench.push("game.run")
      if ctx.editing then
        editor_updateEditor()
        scene_updateScene()
      else

        if not game.paused then
          mouse_setCursor(defaultCursor)
          scene_updateScene()
        end

        if input_keyPressed("escape") then editor_resumeEdit() end
      end
      bench.pop("game.run")
    end
    drawTrace(true)
    bench.pop("update")
  else
    splashUpdate()
  end

  mouse_reset()
  input_keysReset()


  -- DRAW
  gfx.reset()

  if engine.project then

    bench.push("offscreen")
    if shaders.forwardRender then
      for s, shader in pairs(shaders) do
        -- shaders.forwardRender:send('screenSize',{screenWidth,screenHeight})
      end
      shaders.forwardRender:send("time", time)
      if patterns.dims then
        -- shaders.forwardRender:send("patterns",unpack(patterns.dims))
        -- shaders.deferredStatic:send("patterns",unpack(patterns.dims))
      end
    end

    if animatePanel and not (animatePanel.open or animatePanel.active) then
      gfx.setShader()
      gfx.setCanvas(engine.canvas)
      local color
      if #game.views==1 and not ( ctx.roomZoom > 0) then
        color = game.views[1].room.sky.color
      else
        color = ctx.page.sky.color
      end
      gfx.clear(color.r, color.g, color.b, 1, 1, 1)
    end

    for v, view in pairs(game.views) do
      view_drawViewContents(view)
    end
    bench.pop("offscreen")

    engine.halfTime = engine_getTime()-engine.startTime

    if _TEMPTESTCLICK then print("hmm")_TEMPTESTCLICK() _TEMPTESTCLICK = nil end

    bench.disable()
    gfx.beginPass(true)
    bench.enable()

    engine.lastTime = engine_getTime()

    bench.push("final pass")
    bench.push("draw views")
    for v, view in pairs(game.views) do
      view_drawViewToScreen(view)

      local render = view.room.lighting.render
      if render and render.rendering then
        gfx.draw(render.canvas)
        gfx.draw(render.trisTex, 0, 0, 0, 1, 1)
      end

      -- if view.gbuffer and view.gbuffer.diffuse then
      --  gfx.draw(view.gbuffer.diffuse,0,0,0,0.2,0.2)
      --  gfx.draw(view.gbuffer.normal,500,0,0,0.2,0.2)
      --  gfx.draw(view.gbuffer.position,1000,0,0,0.2,0.2)
      -- end
    end
    bench.pop("draw views")

    if game.state == "Edit" then
      bench.push("draw UI")
      -- trace("ctx nextRoom",type(ctx.nextView))
      editor.autoSaveGraphic.x = screenW - 20
      editor.autoSaveGraphic.line = true
      save_draw(editor.autoSaveGraphic)
      if animatePanel.open then
        drawSelectionFrame(animatePanel)
        drawAreaSelected(animatePanel)
      else
        plan_drawRoomStack()
        editor_drawSelectRect()
      end
      for p, panel in ipairs(editor.panels) do
        if panel.open and panel.draw and panel.visible then
          editor_drawPanel(panel)
          panel.draw(panel)
        end
      end

      for b,button in ipairs(editor.buttonQueue) do
        editor_drawButton(button)
      end
      for d,draw in ipairs(editor.drawQueue) do
        draw()
      end

      editor_drawLoupe()
      editor_drawContextMenu(contextMenu)
      editor_drawColorTip()
      editor_drawTip(editor.toolTip)
      drawTrace()
      drawDebug()
      bench.pop("draw UI")
    end

    gfx.setBlendMode("alpha")
    gfx.setDepthMode("always")
    gfx.reset()
    -- light_renderDebug()


    game.resolution = game.newRes
    if game.prevRes ~= game.resolution then
      engine_setDimensions()
      engine_resizeCanvas()
    end
    game.prevRes = game.resolution
    bench.pop("final pass")
  else
    splashDraw()
  end
  endTimeFrame()



  -- TABS
  if #engine.contexts > (engine.project and 1 or 0) then
    local count = engine.project and #engine.contexts or #engine.contexts + 1
    local size = screenW / count
    local x = 0
    for c = 1, count, 1 do
      local context = engine.contexts[c]
      local text = context and context.config.name or "New File"
      local w = math.clamp(#text * 10, 100, 200)
      local y = c ~= engine.currentTab and -8 or 0
      -- print("c",c,text,w)
      size = w + 20
      local hovered
      if c ~= engine.currentTab then
        if mouse_in(x + (size - w) / 2, y, w, 30) then
          hovered = true
          mouse_set(function()
            engine.tabSet = c
          end)
        end
        gfx.setColor(0, 0, 0.1, 0.3 * (hovered and 2 or 1))
        gfx.rectangle("fill", x + (size - w) / 2, y, w, 30, 10)
        gfx.setColor(1, 1, 1, 0.2 * (hovered and 2 or 1))
        gfx.setLineWidth(2)
        gfx.rectangle("line", x + (size - w) / 2, y, w, 30, 10)
      else
        gfx.setColor(0, 0, 0.1, 0.6)
        gfx.rectangle("fill", x + (size - w) / 2, 6 + y, w, 24, 10)
        gfx.setColor(1, 1, 1, 0.5)
        gfx.setLineWidth(3)
        gfx.rectangle("line", x + (size - w) / 2, 6 + y, w, 24, 10)
      end
      if c == engine.currentTab then
        gfx.setColor(1, 1, 1, 1)
      else
        gfx.setColor(1, 1, 1, 0.5 * (hovered and 2 or 1))
      end
      gfx.fontSize(16)
      gfx.printf(text, mf(x + 0.25), 10 + y, size, "center")
      x = x + size
    end
  end
  gfx.drawBatch()

  gfx.finalPass = false
  if gfx.state.inPass then
    -- ffi.C.reset_sgl(engine.window)
    ffi.C.drawFons(engine.window.thread)
    ffi.C.sgl_draw()
    ffi.C.sg_end_pass()
  end

  engine.resizingWindow = false

  if engine.window.close or (cmdDown and input_keyPressed("q")) then 
    print("manual")
    engine_quit() 
  end

end

function engine_quit()
  if animatePanel and animatePanel.newImage and animatePanel.instance then 
    gallery_deleteImage(animatePanel.instance.key) 
  end

  clearTempProject()
  load_saveConfig()
  ffi.C.sapp_request_quit()
end

function engine_run(id)
  engine.window = ffi.C.getWindow(id)
  engine.window.resized = true

  print("missing_img_id", missing_img_id)

  -- gfx.default_sg_image =
  print("DPI SCALE ---------------------------------", engine.window.dpi_scale)
  local ok, err = xpcall(run, debug.traceback)
  if not ok then
    print(err)
    os.exit()
  end
end

function engine_frame()
  -- print(engine.window.frame_time)
  engine.dt = engine.window.frame_time
  local ok, err = xpcall(frame, debug.traceback)
  if not ok then
    print(err)
    os.exit()
  end
end

function engine_getTime()
  local num = tonumber(ffi.C.stm_sec(ffi.C.stm_now()))
  return num
end

function engine_tabKeys()
  if cmdDown and #engine.contexts > 0 then
    if input_keyPressed("w") then
      if engine.project then
        engine.closedTab = true
      else
        engine.tabSet = engine.prevTab
      end
    end
    if shiftDown and input_keyPressed("n") then engine.callNew = true end

    if shiftDown and input_keyPressed("[") then
      engine.tabSet = wrap2(engine.currentTab - 1, 1, #engine.contexts)
      print(engine.tabSet)
    elseif shiftDown and input_keyPressed("]") then
      engine.tabSet = wrap2(engine.currentTab + 1, 1, #engine.contexts)
      print(engine.tabSet)
    end
  end
end

function engine_loadProject(project, new, openedFile)
  _time()
  for c, context in ipairs(engine.contexts) do
    if context.project == project then
      switchContext(c)
      return
    end
  end

  --if engine.project == project then return end
  local extension = config.projects[project].fileType
  print("ext")
  local filename = config.projects[project].dir .. "/" .. config.projects[project].name .. extension
  print(ansicolors("\n%{bluebg} LOAD PROJECT: %{reset} " .. project .. "\n"))
  local check = (filename .. "/." .. config.projects[project].id):gsub("//", "/")
  print(" check " .. check)
  local zip = not exists(check)

  print(" ctx.zip " .. tostring(zip))

  if not openedFile and not new then
    if not exists(filename) then
      print("No proj")
      engine.project = nil
      config.projects[project] = nil
      load_saveConfig()
      return
    end
  end

  engine.files[project] = {}

  table.insert(engine.contexts, {
    file = engine.files[project],
    project = project,
    config = config.projects[project],
    isAnim = config.projects[project].isAnim,
    zip = zip,

    rooms = {},
    dialogue = {},
    symbols = {},
    tags = {},
    scripts = {},
    global = {},

    page = {},
    views = {},
    viewport = {},
    levels = {},
    tagged = {},
    level = {},

    editor = {},
    game = {
      vectorLayers = { "rough", "fill", "line", "note" },
      
      state = "Load",
      newRes = 1,
      resolution = 1,
      playRes = 1,
      shadowRes = 1024,
      patterns = {},
      skeletons = {},
      waiting = {},
      lang = "en",
      characters = { project = {} },
      aspectRatio = gfx.getWidth() / gfx.getHeight(),
      symbolsLoaded = {},

      
    },
    namer = {
      enabled = false,
      index = 1,
      type = "nil",
      name = "",
      text = "",
      alertText = "",
      alertTimer = 0,
      alertMax = 180,
      backOpacity = 0,
      maxOpacity = 0.7,
      mode = "new",
      width = 250,
      height = 160,
      x = 400,
      y = 800,
      target = { x = 0, y = 0 },
      options = {},
    },

    contextMenu = { x = 0, y = 1, items = {} },
    controls = {},
    patterns = {},
    shaders = {},
    roomZoom = 0,
    pageView = true,
    toLoad = {},
    loaded = false,
    sidebar = {},
    testModels = {},
    textures = {},

    planPanel = plan_define(),
    galleryPanel = gallery_define(),
    pathsPanel = paths_definePanel(),
    scriptsPanel = editor_defineScriptsPanel(),
    animatePanel = editor_defineAnimatePanel(),
  })

  ffi.C.setWindowSize(1440,1080)

  switchContext(#engine.contexts)

  ctx.config.modtime = os.time()

  engine.project = project

  local cproj = config.projects[engine.project]

  local str = ""
  if openedFile or not new then
    local cmd
    if ctx.zip then
      local dest = quote(SAVE_DIR .. "projects/" .. config.projects[project].id)
      os.execute(os.rm .. " " .. dest)
      if ffi.os == "Windows" then
        cmd = string.format("mkdir %s && cd %s && tar -xf %s", dest, dest, quote(filename))
      else
        cmd = string.format("unzip -o -q %s -d %s && chmod -R 0777 %s", quote(filename), dest, dest)
      end
      print("exec", cmd)
      os.execute(cmd)
    else
      local proj = (cproj.dir .. "/" .. cproj.name .. extension):gsub("//", "/")
      print(" link \n    " .. proj .. "  to  \n    " .. SAVE_DIR .. "projects/")
      os.link(proj, SAVE_DIR .. "projects/")
      local name1 = SAVE_DIR .. "projects/" .. cproj.name .. extension
      local name2 = SAVE_DIR .. "projects/" .. cproj.id

      lfs.move(name1, name2)
    end
  end

  if game.state == "Edit" then
    engine_exit()
  elseif game.state == "Load" then
    LINK_DIR = "projects/" .. engine.project .. "/"
    GAME_DIR = (cproj.dir .. "/" .. cproj.name .. extension .. "/"):gsub("//", "/")
    PROJ_DIR = (ctx.zip and SAVE_DIR .. LINK_DIR or GAME_DIR):gsub("//", "/")

    print(" LINK_DIR", LINK_DIR)
    print(" GAME_DIR", GAME_DIR)
    print(" PROJ_DIR", PROJ_DIR)

    -----------------------------------------
    local configFile = (LINK_DIR .. "/." .. engine.project):gsub("//", "/")
    local cfg = lfs.read(configFile)
    print(" CONFIG_FILE", configFile)
    local conf = loadstring(cfg)()

    for i, v in ipairs({ "levels", "dialogue", "symbols", "tags", "scripts", "global" }) do
      conf[v] = conf[v] or {}
      ctx[v] = conf[v] or {}
    end
    print("")
    -- TODO dont search on game release build

    local items = lfs.getDirectoryItems(LINK_DIR .. "rooms/")
    print(" > check ", #items, "rooms in ", LINK_DIR .. "rooms")

    for i, item in ipairs(items) do
      print("    new", i, item.name)
      local dir = LINK_DIR .. "rooms/" .. item.name
      if lfs.exists(dir) then
        ctx.rooms[item.name] = lfs.dofile(dir)
        -- print(inspect(ctx.rooms[item.name]))
      end
    end

    if lume.count(ctx.levels) == 0 then
      print("no levels found, add one")
      engine_addLevel("Untitled Level")
    end

    local proj = config.projects[engine.project]
    print("project:",inspect(proj))

    print("")
    print("levels:", lume.count(ctx.levels))
    local current =  proj.level
    if not ctx.levels[proj.level] then
      for k,v in pairs(ctx.levels) do
        current = k
        break
      end
    end
    print(" > current level:", current)
    ctx.level = ctx.levels[current]

    print(" > zip:",ctx.zip)
    if ctx.zip then
      ctx.isAnim = true
    elseif proj.page then
        print("   > current set: ", proj.page)
        print("   > current room:", proj.room)
        print("   > page view:   ", proj.pageView)
        editor.editingPage = proj.page
        ctx.page = ctx.level.pages[editor.editingPage] or ctx.level.pages[ctx.level.defaultPage]

    end

    ------------------------------------------
    -- local file = ""
    -- for s, script in pairs(scripts) do
    --   script.filename = "scripts/" .. script.key
    --   file = LINK_DIR .. script.filename .. ".lua"
    --   if lfs.exists(file) then
    --     if pcall(require, script.filename) then
    --       local lines = 0
    --       for line in lfs.lines(file) do
    --         lines = lines + 1
    --       end
    --       script.lines = lines
    --       script.broken = false
    --     else
    --       script.broken = true
    --     end
    --   else
    --     scripts[s] = nil
    --   end
    -- end

    load_preLoad()
  end
  print(ansicolors("%{greenbg} PROJECT LOADED %{reset}\n"))
  _time("load time")
  
end

function engine_exit()
  clearTempProject()
  engine.project = nil
  load_saveConfig()
  engine.splash.refresh = true
  print("restart")
  -- for k,v in pairs(_G) do
  --   local tipe = type(v)
  --   if tipe == "table" then
  --     if tipe=="table" then
  --       local count = 0
  --       local allFunctions=true
  --       for k2,v2 in pairs(v) do
  --         count=count+1
  --         if type(v2)~="function" then
  --           allFunctions=false
  --         end
  --       end
  --       if not allFunctions then
  --         print(k,tipe,count)
  --       end
  --     else
  --       print(k,tipe)
  --     end
  --   end
  -- end

  engine.setRestart = true
end


function engine_writeProjectFile(data, ...)
  print_info(3)
  local folders = { ... }
  local path = PROJ_DIR
  if #folders > 1 then
    for i = #folders, 2 do
      table.insert(folders, i, "/")
    end
  end
  path = path .. table.concat(folders)
  print("engine_writeProjectFile", path)
  lfs.write(data, path,true)
end

function engine_addLevel(name)
  if levelExists(name) then
    namer_nameTaken()
    return 0
  end

  local newKey = uuid()
  local newLevel = deepcopy(engine.defaultLevel)
  table.insert(ctx.levels, newKey)
  newLevel.key = newKey
  newLevel.name = name

  ctx.levels[newKey] = newLevel
end

function engine_loadGame()
  -- load symbols
  if not load_isLoaded() then load_loadLoop() end

  -- draw load progress
  gfx.setCanvas(engine.canvas)
  gfx.setShader()
  gfx.setBlendMode("alpha")
  gfx.clear(0.1, 0.13, 0.18)
  gfx.setBlendMode("alpha")
  gfx.setColor(1, 1, 1, 1)
  local v1 = (3 * time + 3) % (pi * 2)
  local v2 = (7 * time + 7) % (pi * 2)
  local v3 = (13 * time + 13) % (pi * 2)
  gfx.arc("line", "open", screenW / 2, screenH / 2, 20, v1, v1 + 0.5, 64)
  gfx.arc("line", "open", screenW / 2, screenH / 2, 20, v2, v2 + 0.5, 64)
  gfx.arc("line", "open", screenW / 2, screenH / 2, 20, v3, v3 + 0.5, 64)

  -- finished loading
  if load_isLoaded() and not game.loaded then
    load_tagImages()
    load_loadController()
    initDisplay()

    _time()
    game.saveExists = lfs.exists("project.sav")

    if engine.devMode then
      game.state = "Edit"
      editor.state = "Normal"
      editor.saveStep = 0
      ctx.editing = true
      editor.startTime = time
      mouse_setVisible(true)
    else
      ctx.editing = false
      game.state = "Game"
      game_setResolution(game.playRes)
    end
    setResolution()
    game_setPatterns()
    editor_defineEditor()

    scene_load()
    engine_resizeCanvas()
    game_unPause()

    if ctx.isAnim then editor_createAnimation() end

    game.loaded = true
    _time("enter game")
    print()
  end
end

function engine_loadSource(swap, clear)
  local folders = {
    editor = {
      "animate",
      "gallery",
      "plan",
      "script",
      "paths",
      "click",
      "menu",
      "editor",
      "mesh",
      "save",
      "shortcuts",
      "fill",
      "draw",
      "timeline",
      "flood",
      "bool",
      "stats",
      "tests",
    },
    core = {
      "engine",
      "input",
      "load",
      "gfx",
      "entity",
      "camera",
      "floors",
      "game",
      "light",
      "scene",
      "view",
      "vector",
      "misc",
    },
  }

  -- for k,v in pairs(_G) do
  --  if type(v)=="function" then
  --    print(k)
  --  end
  -- end

  -- for k,v in pairs(_G) do
  -- if type(v)~="function" then
  --  print(k,v)
  -- end
  -- end

  local RENAME = false
  local TRACE = false

  if TRACE and not lfs.isFused() and not _INTROSPECTED then
    _INTROSPECTED = true
    local tables = lume.merge(folders.editor, folders.core)
    local red = "%{redbg}"
    local green = "%{green}"
    local reset = "%{reset}"
    local procs = {}
    local files = {}
    for f, folder in pairs({ "editor", "core" }) do
      for s, sourceName in ipairs(folders[folder]) do
        local path = string.format("%s.lua", folder, sourceName)
        local file = io.open(ENGINE_DIR .. path)
        if file then
          print(path)
          table.insert(files, { path = path, name = sourceName })
          lines = file:lines()
          local l = 0
          for line in lines do
            l = l + 1
            -- print(line)
            -- for n, name in ipairs(tables) do
            if string.find(line, "function") == 1 then --string.format("function %s", name).."%_") then
              -- print("FOUND",name)
              -- print(line)
              local n = string.find(line, "%(")
              local name = string.sub(line, 10, n - 1)
              local new = name:gsub("%.", "_")
              print(" ", name)
              table.insert(procs, {
                path = path,
                source = sourceName,
                name = name,
                new = new,
                line = l,
              })
            end
            -- end
          end
          file:close()
        end
      end
    end
    -- os.exit()
    print("total procs: ", #procs)
    local pfile = ""
    local localizable = 0
    local ploc = false
    for p, proc in ipairs(procs) do
      if true then --proc.source == "plan" then
        -- if p>1 then break end
        if proc.source ~= pfile then
          print()
          print("--------------------------------")
          print(ansicolors("%{bluebg}FILE: " .. proc.path .. "%{reset}"))
          print()
          pfile = proc.source
        end
        usedIn = 0
        for s, source in ipairs(files) do
          local file = io.open(ENGINE_DIR .. source.path)
          lines = file:lines()
          local contents = {}

          local l = 0
          for line in lines do
            l = l + 1
            if string.find(line, proc.name) then
              if RENAME then
                local renamed = string.gsub(line, proc.name, proc.new)
                table.insert(contents, renamed)
              end
              -- local n = string.find(line, "%(")
              -- local name = string.sub(line,11,n-1)
              if source.name ~= proc.source then
                if usedIn == 0 then
                  -- print()
                end
                usedIn = usedIn + 1
                -- print("  ",ansicolors("%{red}"..source.path..":"..tostring(l)..reset))
              end
            else
              if RENAME then table.insert(contents, line) end
            end
          end
          file:close()
          if RENAME then
            local out = io.open(ENGINE_DIR .. source.path, "w")
            for index, value in ipairs(contents) do
              out:write(value .. "\n")
            end
            io.close(out)
          end
        end
        if usedIn == 0 then
          if not ploc then
            -- print()
          end
          ploc = true
          localizable = localizable + 1
        else
          ploc = false
        end

        if usedIn == 0 then
          local col = usedIn == 0 and red or ""
          print(
            ansicolors(
              col
                .. string.format("  %s %s:%i", proc.name, proc.path, proc.line)
                .. "%{reset}  "
                .. (usedIn > 0 and "\n    used in " .. tostring(usedIn) .. " different files" or "")
            )
          )
        end
      end
    end
    print()
    print("------------------------------------------")
    print(ansicolors("%{bluebg}" .. string.format("%i/%i localizable functions", localizable, #procs) .. reset))
    print("------------------------------------------")
    print()
  end

  if clear then
    for f, folder in ipairs({ "core", "editor" }) do
      f, folder = folder, folders[folder]
      for s, sourceName in ipairs(folder) do
        local sourcePath =  f .. "/" .. sourceName
        if f ~= "shaders" then
          -- print(sourcePath)
          -- print("unload",sourcePath,package.loaded[sourcePath])
          package.loaded[sourcePath] = nil
          package.loaded[sourcePath:gsub("/", ".")] = nil
        end
      end
    end
    return
  end

  local succ
  shaderSource = shaderSource or {}
  print(ansicolors("%{greenbg} Reload Source %{reset}"))

  for f, folder in ipairs({ "core", "editor" }) do
    f, folder = folder, folders[folder]
    local isShader = f == "shaders"
    local ext = (isShader and ".glsl" or ".lua")
    for s, sourceName in ipairs(folder) do
      local sourcePath =  f .. "/" .. sourceName
      local sourceModTime = lfs.getLastModified(sourcePath .. ext)
      -- print(sourceModTime, engine.sourceModTimes[sourceName],sourcePath)
      if not engine.sourceModTimes[sourceName] or sourceModTime > engine.sourceModTimes[sourceName] then
        if engine.sourceModTimes[sourceName] then print("reload", sourcePath .. ext, sourceModTime) end
        engine.sourceModTimes[sourceName] = lfs.getLastModified(sourcePath .. ext)
        if swap then
          local succ, ess = pcall(lume.hotswap, sourcePath)

          if succ and ess then
            print(succ, ess, f, sourceName, " required")
          else
            print(ansicolors("%{redbg}\n   FAILED TO REQUIRE : " .. sourcePath .. "\n%{reset}"))
          end
        else
          local time = engine_getTime()
          local succ, ess = pcall(require, sourcePath)
          if succ and ess then
            print(string.format(ansicolors("%{green}loaded %.2fms%{reset} %s.%s"), (engine_getTime() - time) * 1000, f, sourceName))
          else
            print(ansicolors("%{redbg}\n   FAILED TO REQUIRE : " .. sourcePath .. "\n"))
            print(succ,ess)
            -- require(sourcePath)
          end
        end
      end
    end
  end

  engine_setPanels()

  collectgarbage("collect")
  _time()
end

function engine_setDimensions()
  screenW = gfx.getWidth()
  screenH = gfx.getHeight()

  if game then
    renderW = screenW * game.resolution
    renderH = screenH * game.resolution
  end
end

function engine_resizeCanvas()
  if engine.canvas then engine.canvas:release() end
  -- print("engine.canvas", renderW, renderH)
  engine.canvas = newCanvas(renderW, renderH)
end

function engine_manualGC(time_budget, safetynet_megabytes, disable_otherwise)
  local max_steps = 1000
  local steps = 0
  local start_time = engine_getTime()
  while engine_getTime() - start_time < time_budget and steps < max_steps do
    collectgarbage("step", 1)
    steps = steps + 1
  end
  if not lfs.isFused() then
    -- trace("sweep time",(engine_getTime() - start_time),"steps",steps)
  end
  -- print(engine_getTime(),"steps",steps)
  --safety net
  if collectgarbage("count") / 1024 > safetynet_megabytes then
    print("FULL COLLECTION")
    collectgarbage("collect")
  end
  --don't collect gc outside this margin
  if disable_otherwise then collectgarbage("stop") end
  collectgarbage("stop")
end

function engine_waitFor(func, ...)
  table.insert(game.waiting, func)
end
