Aller au contenu

Wrappers temporaires — règle d'or

Les classes RitnLib* runtime sont des vues, pas des entités. Tu en crées une dans un handler, tu l'utilises, tu la jettes.

C'est la règle à connaître avant de toucher à RitnLibPlayer, RitnLibEntity, RitnLibSurface, RitnLibForce, RitnLibEvent, RitnLibRecipe, RitnLibTechnology, RitnLibGui. Si tu l'ignores, tu vas tomber sur des bugs étranges, intermittents, qui survivent aux saves.

Pourquoi

Quand tu instancies un wrapper, son constructeur fait une capture statique des propriétés Factorio. Exemple avec RitnLibPlayer :

RitnLibPlayer = ritnlib.classFactory.newclass(function(self, LuaPlayer)
    self.player = LuaPlayer
    self.index = LuaPlayer.index
    self.surface = LuaPlayer.surface            -- ← capture
    self.force = LuaPlayer.force                -- ← capture
    self.controller_type = LuaPlayer.controller_type  -- ← capture
    self.controller_name = getControllerName(LuaPlayer)
    self.character = LuaPlayer.character        -- ← capture
    self.driving = LuaPlayer.driving            -- ← capture
    self.vehicle = LuaPlayer.vehicle            -- ← capture
    self.connected = LuaPlayer.connected        -- ← capture
    -- ...
end)

Toutes ces propriétés changent au cours du jeu : un joueur change de surface, passe en mode édition (controller_type change), monte dans un véhicule, se déconnecte. Le wrapper, lui, garde ses valeurs initiales.

Si tu réutilises le wrapper plus tard, tu lis des valeurs obsolètes — sans qu'aucune erreur ne soit levée. Le self.player reste valide (c'est une référence LuaPlayer) mais self.surface, self.driving, etc. ne reflètent plus la réalité.

Bonne pratique

Instancie le wrapper dans le handler qui en a besoin, juste avant de l'utiliser :

script.on_event(defines.events.on_player_changed_surface, function(event)
    local player = RitnLibPlayer(game.get_player(event.player_index))
    if player.isPresent then
        player:print("Tu es maintenant sur " .. player.surface.name)
    end
end)

Le wrapper meurt en sortie de fonction. Au prochain event, tu en crées un nouveau, qui reflète l'état du moment.

À ne pas faire

❌ Stocker le wrapper dans storage

-- NE FAIS PAS ÇA
script.on_init(function()
    storage.player_wrapper = RitnLibPlayer(game.get_player(1))
end)

Pourquoi c'est cassé : 1. Au save, Factorio sait sérialiser le LuaPlayer interne (c'est un userdata géré). Mais les copies scalaires (self.surface, self.controller_type…) sont sérialisées comme des valeurs figées au moment de l'init. 2. Au load, Factorio restaure ces scalaires tels quels — même si le joueur a changé de surface dix fois entre temps. 3. Tu te retrouves avec un objet dont self.surface pointe vers une LuaSurface qui n'est plus la surface courante du joueur.

❌ Garder le wrapper entre deux events

-- NE FAIS PAS ÇA
local cached_wrapper

script.on_event(defines.events.on_player_created, function(event)
    cached_wrapper = RitnLibPlayer(game.get_player(event.player_index))
end)

script.on_event(defines.events.on_player_changed_surface, function(event)
    cached_wrapper:print("Nouvelle surface : " .. cached_wrapper.surface.name)
    -- ⚠ cached_wrapper.surface est l'ANCIENNE surface, pas la nouvelle
end)

La variable cached_wrapper survit entre les events (elle est dans l'upvalue de control.lua), mais ses champs scalaires sont gelés au moment de l'instanciation.

❌ Réutiliser un RitnLibEvent au-delà de son event

-- NE FAIS PAS ÇA
local last_event

script.on_event(defines.events.on_built_entity, function(event)
    last_event = RitnLibEvent(event)
end)

script.on_event(defines.events.on_tick, function()
    if last_event then
        log("Dernière entité : " .. last_event.entity.name)
        -- ⚠ last_event.entity peut avoir été détruite depuis
    end
end)

Le LuaEntity interne peut avoir été invalidé (entity.valid == false) — toutes les opérations sur le wrapper lèveront une exception.

Cas où c'est OK de conserver

Tu peux stocker en storage les valeurs primitives extraites du wrapper, jamais le wrapper lui-même :

-- ✅ OK
script.on_event(defines.events.on_player_created, function(event)
    local p = RitnLibPlayer(game.get_player(event.player_index))
    storage.player_names = storage.player_names or {}
    storage.player_names[p.index] = p.name  -- on stocke la string, pas le wrapper
end)

Ou stocke le LuaPlayer brut (Factorio sait le persister) et re-wrappe à chaque besoin :

-- ✅ OK aussi
script.on_event(defines.events.on_player_created, function(event)
    storage.tracked_players = storage.tracked_players or {}
    table.insert(storage.tracked_players, game.get_player(event.player_index))
end)

script.on_event(defines.events.on_tick, function()
    for _, raw_player in pairs(storage.tracked_players or {}) do
        if raw_player.valid then
            local p = RitnLibPlayer(raw_player)  -- wrapper neuf à chaque tick
            -- ...
        end
    end
end)

Récapitulatif

✅ Fais ❌ Évite
RitnLibPlayer(player) dans le handler storage.wrapper = RitnLibPlayer(...)
Re-instancier à chaque besoin Réutiliser un wrapper d'un event à l'autre
Stocker le LuaPlayer brut Stocker le wrapper
Stocker des primitives extraites (string, index, position) Stocker l'objet wrapper

Classes concernées

Toute classe sous classes/LuaClass/ : - RitnLibEvent - RitnLibPlayer - RitnLibSurface - RitnLibForce - RitnLibEntity - RitnLibRecipe - RitnLibTechnology - RitnLibGui - RitnLibInformatron

Exception : RitnLibInventory est un cas particulier — la table qu'il reçoit en deuxième argument est par contre persistante (c'est même le but). Voir Persistance déléguée pour le détail.

Voir aussi