Necrown - Devlog #09 [Engine Dev] : Versioning et auto-génération de l'historique des versions
Splix Il y a 7 mois Premium Pro - Adhésion à vie0

Salut les gamecodeurs et gamecodeuses!!!

Aujourd’hui nous allons nous concentrer principalement sur le versionnage de notre jeu. Nous allons créer un fichier game.lua dans la partie Core du moteur, qui gérera l’objet game, que nous avions créé dans le fichier conf dans un des premiers devlogs, et nous allons compléter game.version. Mais tout d’abord, nous allons lui préparer une métatable qui va nous permettre de redéfinir son comportement, ainsi qu’un petit objet local qui enregistre juste le nom des différentes phases de développement:

local devPhase = {}
devPhase[1] = "MockUp"
devPhase[2] = "Proto"
devPhase[3] = "Alpha"
devPhase[4] = "Beta"
devPhase[5] = "Final"

local version_mt = {}

function version_mt.__lt(p_v1, p_v2)
  if p_v1.phase < p_v2.phase then return true 
  elseif p_v1.major < p_v2.major then return true 
  elseif p_v1.minor < p_v2.minor then return true 
  elseif p_v1.patch < p_v2.patch then return true 
  else return false end
end

setmetatable(game.version, version_mt)

La métaméthode __lt permet de définir ce que devra faire game.version lorsqu’on utilisera l’opérateur < (mais également > puisque a > b équivaut à b < a). Nous pourrons utiliser cet opérateur lorsque nous vérifierons la version actuelle du jeu par rapport à la précédente (quand elle existe).

function game.version.getPhase(p_phase)
  if p_phase == nil then p_phase = game.version.phase end
  return devPhase[p_phase]
end

function game.version.get()
  local debug = ""
  if game.version.phase < 5 then debug = debug..game.version.getPhase().." " end
  debug = debug..game.version.major.."."..game.version.minor.."."..game.version.patch
  return debug
end

function game.version.getWithName()
  return game.version.get()..(game.version.name and " : "..game.version.name or "")
end

local upgrade = nil
function game.version.onUpgrade(p_upgrade)
  assert(type(p_upgrade) ~= "function", "Invalid parameter in game.version.onUpgrade")
  upgrade = p_upgrade
end

function game.version.save()
  fileIO.save("Config/version", game.version)
end

function game.version.check()
  if fileIO.exists("Config/version") then
    local previous = fileIO.load("Config/version")
    setmetatable(previous, version_mt)
    
    if previous < game.version then
      if upgrade then upgrade(previous) end
      game.version.save()
    end
  else
    game.version.save()
  end
end

La variable upgrade enregistrera la fonction à appeler lors d’une upgrade, ce qui nous permettra de la créer dans un autre fichier. Vous remarquerez dans la fonction game.version.check que l’on attribue version_mt comme métatable de la table previous, qui correspond à l’ancienne version enregistré dans le fichiers de sauvegarde, ce qui permet d’utiliser l’opérateur < entre game.version et cette dernière… Le reste des fonctions permettra simplement d’afficher la version du jeu. Il ne reste qu’à appeler game.version.check dans love.load et le tour est joué, tout est automatisé! Nous allons maintenant récupérer la version de love et afficher tout ça dans la console lorsque l’on est en inDev:

local maj, min, patch = love.getVersion()
love.version = maj.."."..min.."."..patch

if game.inDev then
  print(game.title.." [InDev] - "..game.version.get())
  print("Love version : "..love.version.."\n")
end

Maintenant nous allons créer quelque chose de marrant: Un système d’historique de version avec des fichiers auto-générés (ils ne le seront évidemment que dans la version inDev). Nous commencerons par ajouter un nouveau dossier dans le dossier SplixEng, que nous appellerons Versioning, et nous ajouterons un fichier versioning.lua dans la partie Core. C’est ce fichier qui se chargera de la génération du reste.

if game.inDev then
  versioning = {}

  function versioning.createHistory()
    local file = io.open("SplixEng/Versioning/versionHistory.lua", "a")
    io.output(file)
    io.write("versionHistory = {}\n")
    io.write("versionHistory.game = \""..game.title.."\"\n")
    io.close(file)
  end
  
  if not fileIO.exists("SplixEng/Versioning/versionHistory.lua") then
    versioning.createFile()
  end

  require("SplixEng/Versioning/versionHistory")
end

Comme vous le voyez, lorsque l’on est en inDev, si le fichier versionHistory.lua n’existe pas, on le créé. C’est un fichier directement généré par le code lua de la fonction versioning.createFile, qui utilise la library io du lua, et dans lequel on créé un objet versionHistory qui enregistre le nom du jeu et contiendra la liste de toutes ses versions.

function versioning.check()
  if #versionHistory == 0 then
    versioning.log()
  else
    if game.title ~= versionHistory.game then
      versioning.reset()
    else
      local last = versionHistory[#versionHistory]
      
      local diff = last.phase ~= game.version.phase or last.major ~= game.version.major
      diff = diff or last.minor ~= game.version.minor or last.patch ~= game.version.patch
      
      if diff then versioning.log() end
    end
  end
  
  local file = io.open("../Saves/versionHistory.txt","r")
  if file ~= nil then 
    io.close(file)
  else
    versioning.genTxt()
  end
  
  versionHistory = nil
  versioning = nil
end

On ajoute la fonction versioning.check à la suite du require précédent. Lorsque versionHistory ne possède pas encore de version enregistré, on appelle la fonction versioning.log, qui permet d’ajouter la version actuelle à versionHistory, directement en ajoutant la ligne dans versionHistory.lua:

function versioning.removeTxt()
  if fileIO.exists("../Saves/versionHistory.txt") then
    os.remove("../Saves/versionHistory.txt")
  end
end

function versioning.log()
  local v = game.version
  
  local log = "{ phase = "..v.phase..", major = "..v.major..", minor = "..v.minor..", patch = "..v.patch
  if v.name ~= nil then
    log = log..", name = \""..v.name.."\" }"
  else
    log = log.." }"
  end
  
  local file = io.open("SplixEng/Versioning/versionHistory.lua", "a")
  io.output(file)
  io.write("table.insert(versionHistory, "..log..")\n")
  io.close(file)
  
  local entry = { phase = v.phase, major = v.major, minor = v.minor, patch = v.patch }
  if v.name ~= nil then entry.name = v.name end
  
  table.insert(versionHistory, entry)
  
  versioning.removeTxt()
end

Sinon si il y a déjà au moins une version de connue, on vérifie qu’il s’agit bien du même jeu (ce qui permettra d’automatiser le reset lorsque l’on importe le moteur dans un nouveau projet), puis on vérifie que la dernière version connue correspond bien à la version actuelle, sinon on appelle la fonction versioning.log. A noter que versioning.log appelle une fonction versioning.removeTxt qui supprime le fichier texte versionHistory.txt quand il existe. Ce fichier contient un récapitulatif de l’historique des versions de notre jeu. Il est généré à la fin de la fonction versioning.check, lorsque l’on détecte qu’il n’existe pas, via la fonction versioning.genTxt, et se trouvera dans un dossier Saves, externe au projet:

function versioning.genTxt()
  local file = io.open("../Saves/versionHistory.txt", "a")
  io.output(file)
  io.write(versionHistory.game.."\n")
  for k, v in pairs(versionHistory) do
    if k ~= "game" then
      io.write(game.version.getPhase(v.phase).." "..v.major.."."..v.minor.."."..v.patch..(v.name and " : "..v.name or "").."\n")
    end
  end
  io.close(file)
end

A la fin de versioning.check, on supprime finalement versionHistory et versioning, qui ne sont là que pour enregistrer l’historique des versions et générer automatiquement cet historique sous forme de fichier texte…

Voilà pour ce devlog! J’espère que vous l’aurez trouvé intéressant et que l’auto-génération de fichiers lua vous inspirera…

Bon code à tous et à la prochaine!!!

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.