Powrót do strony głównej

LuaJIT odpowiednik Scratch: 12k skryptów na 30 FPS

Artykuł analizuje rozwój wysokowydajnego wizualnego interpretera na LuaJIT i LÖVE2D, zdolnego do wykonywania 12289 skryptów na słabym sprzęcie. Opisana modułowa architektura, struktura bloków, rekurencyjna interpretacja i pakowanie do EXE. Kod przykładów zachowany do analizy middle/senior deweloperów.

12 tysięcy skryptów Scratch na LuaJIT: analiza kodu
Advertisement 728x90

Wysokowydajny wizualny interpreter w LuaJIT i LÖVE2D: 12 tysięcy skryptów na słabym sprzęcie

Początkujący programista zrealizował wizualny edytor bloków programistycznych, zdolny uruchomić 12289 skryptów z blokami IF, ROTATE i WHILE TRUE na jednym rdzeniu słabego laptopa przy stabilnych 30 FPS. Projekt zbudowano na LuaJIT z biblioteką LÖVE2D, zapewniającą wieloplatformowość: Windows, macOS, Linux, Android, iOS. Interpreter oddzielono od edytora, kompilacja do samodzielnego EXE już działa.

Wybór LuaJIT podyktowany jest kodem bajtowym i kompilacją JIT dla wysokiej prędkości wykonania. LÖVE2D wykorzystano za prostą składnię i doświadczenie autora z Roblox Studio. VS Code służył jako główny edytor.

Architektura projektu

Projekt podzielono na moduły dla skalowalności:

Google AdInline article slot
  • Main.lua — główny edytor.
  • Micropaint.lua — edytor sprite'ów.
  • GameLoader.lua — ładowarka gier.
  • BlockList.lua — rejestr bloków (518 linii, z funkcją renderowania).

GUI zaimplementowano ręcznie przez love.graphics bez zewnętrznych bibliotek. Przykład rysowania przycisków:

--PRZYCISK: Załaduj
love.graphics.rectangle("line", 230, 10, 120, 40)
love.graphics.print("Załaduj", 240, 10)
--PRZYCISK: Utwórz
love.graphics.rectangle("line", 360, 10, 100, 40)
love.graphics.print("Utwórz", 370, 10)

Obsługa kliknięć ze szczegółowymi komentarzami:

--PRZYCISK Utwórz
if x > 360 and x < 460 and y > 10 and y < 50 then
    createNewGame()
end
--PRZYCISK ZAŁADUJ 
if x > 230 and x < 350 and y > 10 and y < 50 then
    if gload() == "suc" then
        GameLoaded = true 
        love.window.showMessageBox( "GOTOWE!","Gra o nazwie "..game.name.." załadowana")
    else
        love.window.showMessageBox( "BŁĄD","Podczas ładowania wystąpił błąd")
    end
end

Komentarze ułatwiają utrzymanie, choć biblioteki GUI uczyniłyby kod czystszym.

Google AdInline article slot

Struktura bloków i renderowanie

Bloki opisano w tabelach z metadanymi. Przykład bloku losowych liczb:

t[#t+1] = {
    type = 3, --TYP BLOKU 1 - ZWYKŁY 2 - WYKONANIA 3 - DANYCH
    isStarter = false, --CZY TO BLOK POCZĄTKU SKRYPTU???
    DisplayName = "Wartość losowa (od, do)", -- WYŚWIETLANA NAZWA
    name = "random", -- NAZWA SYSTEMOWA
    args = 2, -- LICZBA ARGUMENTÓW
    containBlocksIn = false, -- ZAWIERA BLOKI WEWNĄTRZ
    color = {0, 0, 0}
}

Bloki obsługują zagnieżdżanie: argumenty mogą być innymi blokami. Renderowanie przez RenderBlock() z próbą rekurencji (częściowo zrealizowane). Białe kwadraty — pola wprowadzania.

Interpreter w jednym wątku dla prostoty. Bloki dzielą się na typy: zwykłe, wykonania, dane. Wykonywanie argumentów rekurencyjnie, wynik w args[i].data.

Google AdInline article slot

Interpretacja bloków

Kod interpretera sprawdza name i oblicza wartość:

if name == "getX" then --Pobierz X
        return sprite.pos.x
    elseif name == "getY" then --Pobierz Y
        return sprite.pos.y
    elseif name == "random" then --Losowość
        return love.math.random(tonumber(args[1].data),tonumber(args[2].data))
    elseif name == "getrotation" then --Obrót
        return sprite.rotation
    elseif name == "getcostume" then --Kostium
        return sprite.costumeNumber
    elseif name == "getsize" then --Rozmiar 
        return sprite.size
    elseif name == "mathplus" then --Plus
        return tonumber(args[1].data) + tonumber(args[2].data)
    elseif name == "mathminus" then --Minus
        return tonumber(args[1].data) - tonumber(args[2].data)
    elseif name == "mathmultiply" then --Pomnóż
        return tonumber(args[1].data) * tonumber(args[2].data)
    elseif name == "mathdivide" then --Podziel
        return tonumber(args[1].data) / tonumber(args[2].data)
end

Tablice Lua indeksowane od 1. LÖVE2D nie obsługuje polskiego domyślnie — użyto czcionek systemowych (Comic Sans i inna) przez love.graphics.newFont.

Kompilacja i wdrożenie

Kompilacja do EXE:

  • Love Fusion pakuje DLL i pliki do EXE.
  • Enigma Virtual Box łączy wszystko w jeden plik wykonywalny.

Obsługa APK/HTML w planach. Demonstracja: 12300 wątków z IF/ROTATE/WHILE TRUE przy 30 FPS.

Co ważne:

  • LuaJIT + LÖVE2D zapewniają 12k+ skryptów na słabym CPU.
  • Architektura modułowa: interpreter oddzielony od edytora.
  • Ręczne GUI przez love.graphics, zagnieżdżone bloki z rekurencyjnym wykonaniem.
  • Wieloplatformowa kompilacja do EXE/APK/HTML.
  • Bloki typowane (dane/wykonanie), argumenty obliczane rekurencyjnie.

— Editorial Team

Advertisement 728x90

Czytaj dalej