2009-06-26 4 views
2

Ich bin Blei Entwickler für Bitfighter, ein Spiel, in erster Linie in C++ geschrieben, aber Lua Roboter Spieler Skript. Wir verwenden Lunar (eine Variante von Luna), um die Bits zusammen zu kleben.Erkennung von veralteten C++ Referenzen in Lua

Ich ringe nun mit, wie unsere Lua-Skripten wissen können, dass ein Objekt durch einen Hinweis muss durch den C++ Code gelöscht. Hier

einig Beispiel Roboter-Code (in Lua):

if needTarget then       -- needTarget => global(?) boolean 
    ship = findClosest(findItems(ShipType)) -- ship => global lightUserData obj 
end 

if ship ~= nil then 
    bot:setAngleToPoint(ship:getLoc()) 
    bot:fire() 
end 

Beachten Sie, dass Schiff nur gesetzt, wenn needTarget wahr ist, andernfalls wird der Wert aus einer vorherigen Iteration verwendet wird. Es ist durchaus möglich (wahrscheinlich sogar, wenn der Bot seinen Job gemacht hat :-), dass das Schiff getötet wurde (und sein Objekt wurde durch C++ gelöscht), seit die Variable zuletzt gesetzt wurde. Wenn dies der Fall ist, wird C++ eine Übereinstimmung haben, wenn wir ship: getLoc() aufrufen und normalerweise abstürzen.

Die Frage ist also, wie man am elegantesten mit der Situation und den Schaden begrenzen, wenn (wenn) ein Programmierer einen Fehler macht.

Ich habe einige Ideen. Erstens könnten wir eine Art von Lua-Funktion erstellen, die die C++ Code aufrufen kann, wenn ein Schiff oder ein anderes Element stirbt:

function itemDied(deaditem) 
    if deaditem == ship then 
     ship = nil 
     needTarget = true 
    end 
end 

Zweitens könnten wir eine Art Referenzzählung intelligenten Zeiger auf „magische Weise“ das Problem beheben implementieren . Aber ich hätte keine Ahnung, wo ich damit anfangen soll.

Drittens können wir irgendeine Art von Leblosigkeit Detektor (nicht sicher, wie das funktionieren würde), dass Bots wie so nennen könnte:

if !isAlive(ship) then 
    needTarget = true 
    ship = nil    -- superfluous, but here for clarity in this example 
end 

if needTarget then       -- needTarget => global(?) boolean 
    ship = findClosest(findItems(ShipType)) -- ship => global lightUserData obj 
end 

<...as before...> 

Viertens konnte ich behalten nur die ID des Schiffes, anstatt eine Referenz, und Verwendung, die das Schiff Objekt in jedem Zyklus, wie diese zu erwerben:

local ship = getShip(shipID)     -- shipID => global ID 

if ship == nil then 
    needTarget = true 
end 

if needTarget then       -- needTarget => global(?) boolean 
    ship = findClosest(findItems(ShipType)) -- ship => global lightUserData obj 
    shipID = ship:getID() 
end 

<...as before...> 

Meine ideale Situation auch Fehler werfen intelligent würde. Wenn ich die getLoc() -Methode auf einem toten Schiff ausführen würde, würde ich gerne Fehlerbehandlungscode auslösen, um dem Bot entweder eine Chance zu geben, oder zumindest dem System zu erlauben, den Roboter zu töten und das Problem zu protokollieren vorsichtiger zu sein, wie ich meinen Bot codiere.

Das sind meine Ideen. Ich stehe auf # 1, aber es fühlt sich klobig an (und könnte viel Hin und Her mit sich bringen, da wir viele Objekte mit kurzen Lebenszyklen haben, mit denen wir zu kämpfen haben, von denen die meisten nicht verfolgt werden). Es könnte leicht zu vergessen sein, die Funktion itemDied() zu implementieren. # 2 ist ansprechend, weil ich mag mag, aber habe keine Ahnung, wie es funktionieren würde. # 3 & # 4 sind sehr einfach zu verstehen, und ich könnte meine Totheitserkennung nur auf die wenigen Objekte beschränken, die über die Spanne von mehreren Spielzyklen hinweg interessant sind (höchstwahrscheinlich ein einzelnes Schiff).

Dies hat ein weit verbreitetes Problem sein. Was denkst du über diese Ideen, und gibt es bessere da draußen?

Danke!


Hier ist meine aktuelle beste Lösung:

In C++, mein Schiff Objekt Schiff genannt, dessen Lebenszyklus von C++ gesteuert. Für jedes Schiff erstelle ich ein Proxy-Objekt namens LuaShip, das einen Zeiger auf das Schiff enthält, und Ship enthält einen Zeiger auf das LuaShip.Im Destruktor des Schiffs habe ich den Ship-Zeiger des LuaShips auf NULL gesetzt, was ich als Indikator dafür verwende, dass das Schiff zerstört wurde.

Mein Lua-Code hat nur einen Verweis auf das LuaShip, und so (zumindest theoretisch, da dieser Teil immer noch nicht richtig funktioniert) steuert Lua den Lebenszyklus des LuaShips, sobald das entsprechende Ship-Objekt verschwunden ist. So wird Lua immer ein gültiges Handle haben, auch nachdem das Ship-Objekt weg ist, und ich kann Proxy-Methoden für die Ship-Methoden schreiben, die nach Ship-NULL-Werten suchen.

Jetzt ist es meine Aufgabe, besser zu verstehen, wie Luna/Lunar den Lebenszyklus von Zeigern verwaltet und sicherstellt, dass meine LuaShips nicht gelöscht werden, wenn ihre Partner-Schiffe gelöscht werden, wenn noch etwas Lua-Code darauf zeigt. Das sollte sehr machbar sein.


Eigentlich war es nicht machbar (zumindest nicht von mir). Was zu funktionieren schien, war, das Schiff und die LuaShip-Objekte ein wenig zu entkoppeln. Jetzt, wenn das Lua-Skript ein LuaShip-Objekt anfordert, erstelle ich ein neues und übergebe es an Lua und lasse es von Lua löschen, wenn es fertig ist. Das LuaShip verwendet einen intelligenten Zeiger, um auf das Schiff zu verweisen. Wenn also das Schiff stirbt, wird dieser Zeiger auf NULL gesetzt, was das LuaShip-Objekt erkennen kann.

Es ist Aufgabe des Lua-Coders, vor der Verwendung zu überprüfen, ob das Schiff noch gültig ist. Wenn das nicht der Fall ist, kann ich die Sitation einfangen und eine ernste Fehlermeldung rauswerfen, anstatt das ganze Spiel zum Absturz zu bringen (wie es vorher passierte).

Jetzt hat Lua totale Kontrolle über den Lebenszyklus des LuaShips, C++ kann Schiffe löschen, ohne Probleme zu verursachen, und alles scheint reibungslos zu funktionieren. Der einzige Nachteil ist, dass ich möglicherweise viele LuaShip-Objekte erstelle, aber es ist wirklich nicht so schlimm.


Wenn Sie an diesem Thema interessiert sind, bitte die Mailingliste Thread sehe ich zu einem verwandten Konzept geschrieben, die zur Verfeinerung der oben in einigen Vorschlägen endet:

http://lua-users.org/lists/lua-l/2009-07/msg00076.html

+0

haben Sie etwas aufwendiger versucht, wie [luabridge] (https://github.com/vinniefalco/LuaBridge) oder [luabind] (http://www.rasterbar.com/products/luabind/docs.html)) Beide erlauben Objekte mit gemischten Lebenszeiten (lua/C++) ohne Speicherlecks. –

Antwort

5

Ich glaube nicht, dass Sie ein Problem auf Ihrer Lua-Seite haben, und Sie sollten es dort nicht lösen.

Ihr C++ - Code löscht Objekte, auf die noch verwiesen wird. Egal wie sie referenziert werden, das ist schlecht.

Die einfache Lösung kann sein, Lunar alle Ihre Objekte aufzuräumen. Es weiß bereits, welche Objekte am Leben gehalten werden müssen, weil das Skript sie verwendet, und es scheint machbar, GC auch für zufällige C++ - Objekte ausführen zu lassen (natürlich unter der Annahme von intelligenten Zeigern auf C++ - Seite))

+0

Ja, Sie haben Recht. Ich denke, das Problem ist, wie Luna/Lunar bindet. Ich versuche immer noch, die Details zu verstehen, aber es scheint, schwache Tabellen in seiner Referenzierung zu verwenden, die gc nicht daran hindert, Objekte zu löschen. Hoffentlich kann ich es ein wenig zwicken und sagen, dass es das zumindest für ausgewählte Objekte nicht tun soll (wie das LuaShip, das ich in meiner obigen Lösung beschrieben habe). – Watusimoto

4

Unsere Firma ging mit Lösung Nummer vier, und es hat gut für uns gearbeitet. Ich empfehle es. Der Vollständigkeit halber jedoch:

Nummer 1 ist fest. Lassen Sie den Destruktor des Schiffs irgendeinen Mondcode aufrufen (oder markieren Sie, dass er aufgerufen werden sollte), und beschweren Sie sich dann, wenn Sie ihn nicht finden können. Dinge auf diese Weise zu tun bedeutet, dass Sie unglaublich vorsichtig sein müssen und vielleicht die Lua-Laufzeit ein wenig hacken, wenn Sie die Game Engine und die Roboter in separaten Threads ausführen wollen.

Nummer 2 ist nicht so schwer wie Sie denken: Schreiben oder leihen Sie einen Referenzzähler auf C++ - Seite, und wenn Ihr Lua/C++ - Kleber mit C++ - Zeigern vertraut ist, funktioniert er wahrscheinlich ohne weitere Eingriffe , es sei denn, Sie generieren Bindungen, indem Sie zur Laufzeit oder zur Untersuchung von Symboltabellen eine Überprüfung durchführen. Das Problem ist, es wird eine ziemlich tiefgreifende Veränderung in Ihrem Design erzwingen; Wenn Sie reference-counted pointer verwenden, um sich auf Schiffe zu beziehen, müssen Sie diese überall verwenden - die Risiken, die mit der Bezugnahme auf Schiffe mit einer Mischung aus bloßen Zeigern und intelligenten Schiffen verbunden sind, sollten offensichtlich sein. Also würde ich diesen Weg nicht gehen, nicht so spät im Projekt, wie Sie scheinen.

Nummer 3 ist schwierig. Sie müssen einen Weg finden, um zu bestimmen, ob ein gegebenes Schiffsobjekt lebendig oder tot ist, selbst nachdem der Speicher, der es darstellt, freigegeben wurde. Alle Lösungen, die ich mir für dieses Problem vorstellen kann, gehen grundsätzlich in Nummer 4 über: Sie können toten Schiffen erlauben, eine Art Token zu hinterlassen, die in das Lua-Objekt kopiert wird und damit tote Objekte erkennen kann :: Set oder etwas Ähnliches), aber warum nicht nur Schiffe durch ihre Tokens beziehen?

Im Allgemeinen können Sie nicht feststellen, ob ein bestimmter C++ - Zeiger auf ein Objekt verweist, das gelöscht wurde. Es gibt also keine einfache Möglichkeit, Ihr Problem zu lösen. Das Auffangen des Fehlers beim Aufruf von ship:getLoc() auf einem gelöschten Schiff ist nur möglich, wenn Sie im Destruktor spezielle Maßnahmen ergreifen. Es gibt keine perfekte Lösung für dieses Problem, also viel Glück.

+0

Meine aktuelle Lösung (am Ende meiner Frage veröffentlicht) stößt auf das Threading-Problem, das Sie angesprochen haben. Aber zu diesem Zeitpunkt denke ich nicht, dass Threading sehr sinnvoll ist, da meine Skripts auf einem alten Laptop mit fast 60 Bildern/Sek. Bequem laufen und Threading eine Menge Komplexität hinzufügen würde. Ich verwende eine Art von Smart-Pointer für die LuaShip-Verbindung des Schiffs <=>, also hoffentlich sehe ich die Vorteile dort, ohne das gesamte Spiel nachrüsten zu müssen, was prohibitiv wäre. – Watusimoto

0

Ich stimme mit MSalters überein, ich glaube wirklich nicht, dass Sie den Speicher von der C++ Seite befreien sollten. Lua userdata unterstützt das ___gc-Metamethod, um dir die Möglichkeit zu geben, Dinge aufzuräumen. Wenn das GC nicht aggressiv genug ist, können Sie es ein wenig zwicken oder es manuell mit einer kleinen Schrittgröße, öfter ausführen. Das lua gc ist nicht deterministisch. Wenn also Ressourcen freigegeben werden müssen, benötigen Sie eine Funktion, die Sie aufrufen können, um diese Ressourcen freizugeben (die ebenfalls von __gc mit entsprechenden Prüfungen aufgerufen werden).

Sie könnten auch in die Verwendung von weak tables für Ihre Schiff Referenzen suchen, so dass Sie keine Referenz auf Null zuweisen müssen, um es zu befreien. Haben Sie eine starke Referenz (zB in einer Liste aller aktiven Schiffe), dann sind alle anderen schwache Referenzen. Wenn ein Schiff zerstört wird, setze eine Flagge auf das Schiff, das es als solches markiert, und setze dann den Bezug auf Null in der Tabelle der aktiven Schiffe. Wenn dann das andere Schiff will Ihre Logik zu interagieren, ist das gleiche, außer Sie überprüfen:

if ship==nil or ship.destroyed then 
    ship = findClosest(findItems(ShipType)) 
end 
2

Dies ist eine alte Frage, aber die richtige Lösung, IMO, ist zu haben lua_newuserdata() ein erstellen shared_ptr oder weak_ptr über entweder boost::shared_ptr/boost::weak_ptr oder C++11std::shared_ptr/std::weak_ptr. Von dort erstellen Sie eine Referenz wann immer Sie es brauchen, oder scheitern, wenn die weak_ptrlock() eine shared_ptr nicht erhalten kann. Zum Beispiel (unter Verwendung der Boost-shared_ptr in diesem Beispiel, da dies eine alte Frage ist, wo Sie haben wahrscheinlich nicht C++11 Unterstützung noch haben, obwohl für neue Projekte, wo möglich I ++ C 11s shared_ptr würde empfehlen):

using MyObjectPtr = boost::shared_ptr<MyObject>; 
using MyObjectWeakPtr = boost::weak_ptr<MyObject>; 

auto mySharedPtr = boost::make_shared<MyObject>(); 
auto userdata = static_cast<MyObjectWeakPtr*>(lua_newuserdata(L, sizeof(MyObjectWeakPtr))); 
new(userdata) MyObjectWeakPtr(mySharedPtr); 

und dann, wenn Sie ein C++ Objekt zu erhalten:

auto weakObj = *static_cast<MyObjectWeakPtr*>(
    luaL_checkudata(L, 1, "MyObject.Metatable")); 
luaL_argcheck(L, weakObj != nullptr, 1, "'MyObjectWeakPtr' expected"); 

// If you're using a weak_ptr, this is required!!!! If your userdata is a 
// shared_ptr, you can just act on the shared_ptr after luaL_argcheck() 
if (auto obj = weakObj.lock()) { 
    // You have a valid shared_ptr, the C++ object is alive and you can 
    // dereference like a normal shared_ptr. 
} else { 
    // The C++ object went away, you can safely garbage collect userdata 
} 

Es ist wichtig, dass Sie nicht vergessen, die weak_ptr in Ihrem lua __gc metamethod ausplanen:

static int 
myobject_lua__gc(lua_State* L) { 
    auto weakObj = *static_cast<MyObjectWeakPtr*>(
     luaL_checkudata(L, 1, "MyObject.Metatable")); 
    luaL_argcheck(L, weakObj != nullptr, 1, "'MyObjectWeakPtr' expected"); 
    weakObj.~MyObjectWeakPtr(); 
} 

Vergessen Sie nicht, Verwendung von Makros oder Metaprogrammierung zu vermeiden, viel von der Code-Duplizierung re zu machen: static_cast<>, luaL_argcheck() usw.

Verwenden shared_ptr, wenn Sie die C halten müssen ++, solange das Objekt am Leben Lua-Objekt existiert auch.Benutze weak_ptr, wenn C++ das Objekt ernten kann und es okay ist, dass es unter Luas Füßen verschwindet. Verwenden Sie IMMER entweder shared_ptr oder weak_ptr, wenn die Lebensdauer eines Objekts nicht bekannt ist und automatisch von refcount verwaltet werden muss.

Tipp: Lassen Sie Ihre C++ - Klasse von boost::enable_shared_from_this oder std::enable_shared_from_this erben, da sie die Verwendung von shared_from_this() ermöglicht.