2009-06-18 9 views
5

Jestem głównym programistą dla Bitfighter i używamy Lua jako języka skryptowego, aby umożliwić graczom programowanie własnych niestandardowych statków-robotów.Zgłaszanie zmiennych i pytań dotyczących zakresu Lua

W Lua nie trzeba deklarować zmiennych, a wszystkie zmienne mają domyślny zasięg globalny, chyba że zadeklarowano inaczej. To prowadzi do pewnych problemów. Weźmy następujący fragment, na przykład:

loc = bot:getLoc() 
items = bot:findItems(ShipType)  -- Find a Ship 

minDist = 999999 
found = false 

for indx, item in ipairs(items) do   
    local d = loc:distSquared(item:getLoc()) 

    if(d < minDist) then 
     closestItem = item 
     minDist = d 
    end 
end 

if(closestItem != nil) then 
    firingAngle = getFiringSolution(closestItem) 
end 

W tym fragmencie, jeśli findItems() zwraca żadnych kandydatów, wówczas closestItem nadal będą odnosić się do tego, co statek okaże ostatni raz dokoła, aw czasie interwencyjnym, że statek mógł zostać zabity. Jeśli statek zostanie zabity, przestanie istnieć, a getFiringSolution() zawiedzie.

Czy zauważyłeś problem? Cóż, nie będą też moi użytkownicy. Jest subtelny, ale z dramatycznym efektem.

Jednym z rozwiązań może być wymaganie zadeklarowania wszystkich zmiennych oraz domyślnego ustawienia wszystkich zmiennych na zakres lokalny. Chociaż zmiana ta nie uniemożliwiłaby programistom odnoszenia się do obiektów, które już nie istnieją, utrudniłoby to nieumyślne wykonanie.

Czy jest jakiś sposób, aby powiedzieć Lua, aby domyślnie wszystkie vary na zasięg lokalny, i/lub wymagać, aby zostały zadeklarowane? Wiem, że niektóre inne języki (np. Perl) mają tę opcję.

Dzięki!


Wiele dobrych odpowiedzi tutaj, dzięki!

Postanowiłem wybrać nieco zmodyfikowaną wersję modułu "ścisłego" Lua. To wydaje mi się, że chcę tam, gdzie chcę, i zhakuję ją trochę, aby poprawić wiadomości i uczynić je bardziej odpowiednimi dla mojego konkretnego kontekstu.

+0

BTW, czy masz jakiś dobry powód, aby umieścić dodatkowe szelki wewnątrz swoich instrukcji if? –

+0

Cóż, zanim postawiłeś pytanie, odpowiedziałbym, że są one wymagane. Ale teraz muszę odpowiedzieć, że pomijanie parensów wygląda mi nie tak! – Watusimoto

+2

Oczywiście jest to osobista preferencja. Ale ważne jest, aby pamiętać, że Lua to nie C, Pascal czy cokolwiek innego. Lua to Lua i aby skutecznie z niego korzystać, musisz używać go takim, jaki jest, a nie tak, jakby był słabym zamiennikiem innego języka programowania. Odkryłem, że taka "składnia wygląda źle" sprawia, że ​​Lua staje się częścią mojego mózgu, odmienną od C++ i innych znanych mi języków. Krótko mówiąc, moja opinia brzmi: jeśli jest napisane w Lua, napisz to na sposób Lua! :-) –

Odpowiedz

3

Nie ma opcji, aby ustawić to zachowanie, ale istnieje moduł "ścisły" dostarczany ze standardową instalacją, która dokładnie to robi (przez modyfikację meta-tabel). Użycie: wymaga "rygorystycznego"

Aby uzyskać bardziej szczegółowe informacje i inne rozwiązania: http://lua-users.org/wiki/DetectingUndefinedVariables, ale polecam "ścisłe".

+0

To wydaje się być najprostsza odpowiedź i właśnie tego szukałem.Chciałbym lepiej zrozumieć inne proponowane tutaj rozwiązania, aby sprawdzić, czy istnieją jakieś wady stosowania "ścisłego". – Watusimoto

2

Sorta.

W Lua globale teoretycznie żyją w tabeli globaliów _G (rzeczywistość jest nieco bardziej złożona, ale od strony Lua nie sposób powiedzieć AFAIK). Podobnie jak w przypadku wszystkich innych tabel Lua, możliwe jest dołączenie metatable __newindex do _G, które kontroluje sposób dodawania do niego zmiennych. Niech ta __newindex obsługi robić, co chcesz zrobić, gdy ktoś tworzy globalny: wygeneruje błąd, ale pozwalają go wydrukować ostrzeżenie itp

mieszać z _G, to najprostszym i najczystszych używać setfenv. Zobacz documentation.

0

W rzeczywistości dodatkowa zmienna globalna ze starym odniesieniem do statku będzie wystarczająca, aby GC nie odrzucił obiektu. Tak więc można go było wykryć w czasie wykonywania, zauważając, że statek jest teraz "martwy" i odmawiający zrobienia czegoś z nim. Nadal nie jest to właściwy statek, ale przynajmniej się nie psuje.

Jedna rzecz, którą można zrobić, to zachować skrypty użytkownika w sandbox, prawdopodobnie w piaskownicy w jednym skrypcie.Przy prawidłowej manipulacji tabelą środowiska piaskownicy lub jej metatabilnością można ustawić odrzucenie wszystkich lub większości zmiennych globalnych z piaskownicy przed (lub tuż po) wywołaniem kodu użytkownika.

Oczyszczanie piaskownicy po rozmowach ma tę zaletę, że odrzuca dodatkowe odniesienia do rzeczy, które nie powinny się kręcić. Można to zrobić, utrzymując białą listę pól, które mogą pozostać w środowisku i usuwając całą resztę.

Na przykład za pomocą piaskownicy wywołuje funkcję dostarczoną przez użytkownika ze środowiskiem zawierającym tylko białe nazwy znajdujące się za nową tabelą podstawową dostarczoną dla każdego połączenia.

 
-- table of globals that will available to user scripts 
local user_G = { 
     print=_G.print, 
     math=_G.math, 
     -- ... 
    } 
-- metatable for user sandbox 
local env_mt = { __index=user_G } 


-- call the function in a sandbox with an environment in which new global 
-- variables can be created and modified but they will be discarded when the 
-- user code completes. 
function doUserCode(user_code, ...) 
    local env = setmetatable({}, env_mt) -- create a fresh user environment with RO globals 
    setfenv(user_code, env)  -- hang it on the user code 
    local results = {pcall(user_code, ...)} 
    setfenv(user_code,{}) 
    return unpack(results) 
end 

Można to rozszerzyć, aby tabela globalna była tylko do odczytu, przesuwając ją z powrotem za jeszcze jeden dostęp metatabilny, jeśli chcesz.

Należy pamiętać, że kompletne rozwiązanie do piaskownicy również rozważyłoby, co zrobić z kodem użytkownika, który przypadkowo (lub złośliwie) wykonuje nieskończoną (lub bardzo długą) pętlę lub inną operację. Ogólne rozwiązania tego problemu są sporadycznym tematem dyskusji na temat Lua list, ale dobre rozwiązania są trudne.

+0

Problem z odniesieniem do obiektu martwego statku polega na tym, że czas życia obiektu jest kontrolowany przez główny program C++, więc obiekt można usunąć bez zgody i kontroli Lua. Lua odwołuje się do wskaźnika userdata, który nagle wskazuje na nieprawidłowy obiekt. Jak mogę to wykryć? Muszę się tym lepiej przyjrzeć i byłbym otwarty na wszelkie sugestie. Pracuję na wzór piaskownicy, ale chcę umożliwić tworzenie zmiennych globalnych, o ile są tworzone celowo i celowo. Wszystkie powyższe sugestie zdają się to robić. – Watusimoto

2

"Domyślnie lokalnie jest źle". Proszę zobaczyć

http://lua-users.org/wiki/LocalByDefault

http://lua-users.org/wiki/LuaScopingDiscussion

trzeba użyć pewnego rodzaju globalnej ochrony środowiska. Istnieje kilka statycznych narzędzi do tego (niezbyt dojrzałych), ale najczęstszym rozwiązaniem jest użycie ochrony podczas pracy w oparciu o __index i __newindex w metatable.

Shameles wtyczki: ta strona może być również użyteczne:

http://code.google.com/p/lua-alchemy/wiki/LuaGlobalEnvironmentProtection

Należy zauważyć, że podczas gdy omawia Lua osadzony swf, opisana technika (patrz sources) działają dla rodzajowego Lua. Używamy czegoś wzdłuż tych linii w naszym kodzie produkcyjnym w pracy.

+1

Lokalnie domyślnie jest (prawdopodobnie) źle, ale domyślnie jest globalna! Sprawdziłem kod, który wskazałeś, i wygląda na to, że robi coś bardzo podobnego do modułu "ścisłego" sugerowanego powyżej. Czy różnią się one w jakikolwiek znaczący sposób? – Watusimoto

+0

Globalnie domyślnie też jest źle, ale jest to spuścizna Lua i prawie nie można jej zmienić (również jest to bardzo przydatne przy pisaniu mini-DSL w Lua). –

+0

Główna różnica (pomiędzy tymi ścisłymi modułami) polega na tym, że moja stara się wymusić jednoznaczne i zwięzłe deklarowanie zmiennych globalnych (ponieważ uważam je za złe rzeczy, których należy się pozbyć w moim kodzie - w tym funkcje globalne). Lua's etc/strict.lua jest nieco mniej jednoznaczna. Ale jest to osobiste preferencje. –

Powiązane problemy