Dzięki modelowi programowania Paralelne Luau , możesz uruchamiać kod na wielu wątkach jednocześnie, co może poprawić wydajność Twojego doświadczenia.W miarę rozszerzania doświadczenia o więcej treści możesz przyjąć ten model, aby pomóc utrzymać wydajność i bezpieczeństwo swoich skryptów Luau.
Model programowania równoległego
Domyślnie skrypty są wykonywane kolejno.Jeśli twoje doświadczenie ma złożoną logikę lub zawartość, taką jak postacie bez graczy (NPC), weryfikacja raycastingu i generacja proceduralna, to kolejność wykonania może spowodować opóźnienie dla użytkowników.Dzięki modelowi programowania równoległego możesz podzielić zadania na wiele skryptów i uruchomić je równolegle.To sprawia, że twój kod doświadczenia działa szybciej, co poprawia doświadczenia użytkownika.
Model programowania równoległego dodaje również korzyści bezpieczeństwa do twojego kodu.Rozdzielając kod na wiele wątków, gdy edytujesz kod w jednym wątku, nie wpływa to na inny kod uruchamiany równolegle.Zmniejsza to ryzyko wystąpienia jednego błędu w kodzie, który zniszczy całe doświadczenie, i minimalizuje opóźnienie dla użytkowników na żywych serwerach, gdy wysyłasz aktualizacja.
Przyjęcie modelu programowania równoległego nie oznacza umieszczenia wszystkiego w wielu wątkach.Na przykład, weryfikacja strony serwerowej emisji promieni ustawia każdemu użytkownikowi zdalne wydarzenie równoległe, ale nadal wymaga, aby początkowy kod uruchamiał się seriowo, aby zmienić globalne właściwości, co jest powszechnym wzorem dla równoległego wykonania.
W większości przypadków musisz połączyć fazę seriową i równolelną, aby uzyskać żądany wynik, ponieważ obecnie istnieją niektóre operacje nie wspierane równolegle, które mogą uniemożliwić uruchomienie skryptów, takie jak modyfikowanie instancji w fazach równoległych.Aby uzyskać więcej informacji o poziomie wykorzystania API w parze, zobacz bezpieczeństwo wątków.
Rozdziel kod na wiele wątków
Aby uruchomić skrypty swojego doświadczenia w wielu wątkach jednocześnie, musisz podzielić je na logiczne kawałki pod różnymi aktorami w modelu danych .Aktorzy są reprezentowane przez Actor instancje dziedziczące od DataModel.Funkcjonują jako jednostki izolacji wykonania, które rozdzielają obciążenie na wiele równocześnie działających rdzeni.
Umieść instancje aktora
Możesz umieścić aktorów w odpowiednich pojemnikach lub użyć ich do zastąpienia typów instancji najwyższego poziomu swoich 3D jednostek, takich jak NPC i rzutnicy promieni, a następnie dodać odpowiednie skrypty.

W większości sytuacji nie powinieneś umieszczać aktora jako dziecko innego aktora w modelu danych.Jeśli jednak zdecydujesz się umieścić skrypt zagnieżdżony w wielu aktorach dla swojego konkretnego przypadku użycia, skrypt jest własnością jego najbliższego aktora przodków.

Rozsynchronizuj wątki
Chociaż umieszczenie skryptów pod aktorami zapewnia im możliwość równoległego wykonywania, domyślnie kod nadal uruchamiany jest na jednym wątku seriowo, co nie poprawia wykonywaniewykonania.Musisz wywołać task.desynchronize(), funkcję yieldable, która zawiesza wykonanie obecnej korozyny do wykonywania kodu równolegle i wznowia ją przy następnej okazji równoległego wykonania.Aby przełączyć skrypt z powrotem do wykonywania w trybie szeregowym, wezwij task.synchronize().
Alternatywnie możesz użyć metody RBXScriptSignal:ConnectParallel(), gdy chcesz zaplanować wezwanie sygnału, aby natychmiast uruchomić swój kod równolegle po uruchomieniu.Nie musisz dzwonić task.desynchronize() wewnątrz wezwania sygnału.
Rozsynchronizuj wątek
local RunService = game:GetService("RunService")
RunService.Heartbeat:ConnectParallel(function()
... -- Niektóry kod równoległy, który oblicza aktualizację aktualizacja
task.synchronize()
... -- Jakiś kod serjalny, który zmienia stan instancji
end)
Skrypty, które są częścią tego samego aktora, zawsze wykonują sekwencyjnie względem siebie, więc potrzebujesz wielu aktorów.Na przykład, jeśli umieścisz wszystkie skrypty zachowań włączonych równolegle dla twojego NPC w jednym aktorze, nadal będą one działać seriowo na jednym wątku, ale jeśli masz wiele aktorów dla różnych logiki NPC, każdy z nich działa równolegle na własnym wątku.Aby uzyskać więcej informacji, zobacz Najlepsze praktyki.


Bezpieczeństwo wątku
Podczas równoległego wykonania możesz uzyskać dostęp do większości instancji hierarchii DataModel tak jak zwykle, ale niektóre właściwości i funkcje API nie są bezpieczne do odczytu lub zapisu.Jeśli używasz ich w swoim równoległym kodzie, Roblox Engine może automatycznie wykrywać i zapobiegać wystąpieniu tych dostępów.
Członkowie API mają poziom bezpieczeństwa wątków, który wskazuje, czy i jak możesz ich używać w swoim równoległym kodzie, jak pokazuje poniższy tabela:
Poziom bezpieczeństwa | Dla właściwości | Dla funkcji |
---|---|---|
Niebezpieczny | Nie można odczytać lub zapisać równolegle. | Nie można wezwać równolegle. |
Przeczytaj równolegle | Można go odczytać, ale nie napisać równolegle. | N/A |
Lokalny sejf | Można go używać w tym samym aktorze; można go odczytać, ale nie zapisać przez inne Actors równolegle. | Można wezwać w tym samym aktorze; nie można wezwać przez inne Actors równolegle. |
Bezpieczny | Można go odczytać i zapisać. | Można wezwać. |
Możesz znaleźć tagi bezpieczeństwa wątków dla członków API na odniesieniu API.Podczas ich używania powinieneś również rozważyć, w jaki sposób wezwania API lub zmiany właściwości mogą wchodzić w interakcję między równoległymi wątkami.Zazwyczaj bezpieczne jest, aby wielu aktorów czytało te same dane co inni aktorzy, ale nie modyfikowało stanu innych aktorów.
Komunikacja międzywątkowa
W kontekście wielowątkowym nadal możesz pozwolić, aby skrypty w różnych aktorach komunikowały się ze sobą, wymieniały dane, koordynowały zadania i synchronizowały działania.Silnik wspiera następujące mechanizmy komunikacji między wątkami:
- Komunikacja aktora API do wysyłania wiadomości do aktora za pomocą skryptów.
- Udostępniona struktura danych tabeli do efektywnego dzielenia dużej ilości danych między wieloma aktorami w wspólnym stanie.
- Bezpośrednia komunikacja modelu danych dla prostego komunikatu z ograniczeniami.
Możesz wspierać wiele mechanizmów, aby zaspokoić potrzeby komunikacji między wątkami.Na przykład możesz wysłać współdzielony stół za pośrednictwem API do komunikacji aktora.
Wiadomości aktora
Interfejs API aktora wysyłania pozwala skryptowi, albo w kontekście równoleznym, albo w kontekście szeregowym, wysłać dane do aktora w tym samym modelu danych.Komunikacja za pośrednictwem tego API jest asynchroniczna, w której nadawca nie blokuje, dopóki odbiorca nie otrzyma wiadomość.
Wysyłając wiadomości za pomocą tego API, musisz określić tekst tematu do kategoryzacji wiadomości.Każda wiadomość może być wysłana tylko do jednego aktora, ale ten aktor może wewnętrznie mieć wiele powrotów związanych z wiadomością.Tylko skrypty, które są potomkami aktora, mogą otrzymywać wiadomości.
API ma następujące metody:
- Actor:SendMessage() do wysyłania wiadomości do aktora.
- Actor:BindToMessage() do wiązania powrotu Luau z wiadomością z określonym tematem w kontekście szeregowym.
- Actor:BindToMessageParallel() do wiązania powrotu Luau z wiadomością z określonym tematem w kontekście równoległym.
Poniższy przykład pokazuje, jak użyć Actor:SendMessage() do określenia tematu i wysłania wiadomości na kończyćnadawcy:
Przykładowy nadawca wiadomości
local Workspace = game:GetService("Workspace")-- Wyślij dwie wiadomości do aktora pracownika z tematem "Powitanie"local workerActor = Workspace.WorkerActorworkerActor:SendMessage("Greeting", "Hello World!")workerActor:SendMessage("Greeting", "Welcome")print("Sent messages")
Poniższy przykład pokazuje, jak użyć Actor:BindToMessageParallel(), aby wiązać powrotną funkcję dla określonego tematu w równoległym kontekście na kończyćodbiorcy:
Odbiorca wiadomości przykładowy
-- Zdobądź aktora, do którego ten skrypt jest powiązany
local actor = script:GetActor()
-- Zwiąż powrotną funkcję dla tematu wiadomości "Powitanie"
actor:BindToMessageParallel("Greeting", function(greetingString)
print(actor.Name, "-", greetingString)
end)
print("Bound to messages")
Wspólny stół
SharedTable jest to struktura danych tablicowa dostępna z poziomu skryptów uruchamianych pod wieloma aktorami.Jest przydatny w sytuacjach, które wiążą się z dużą ilością danych i wymagają wspólnego wspólnego stanu między wieloma wątkami.Na przykład, gdy wielu aktorów pracuje nad wspólnym stanem świata, który nie jest przechowany w modelu danych.
Wysyłanie wspólnego stołu do innego aktora nie tworzy kopii danych.Zamiast tego współdzielone tabele umożliwiają bezpieczne i atomowe aktualizacje przez wiele skryptów jednocześnie.Każde aktualizowanie wspólnego stołu przez jednego aktora jest natychmiast widoczne dla wszystkich aktorów.Udostępnione tabele można również sklonować w procesie wysokowydajnym, który wykorzystuje udostępnianie strukturalne zamiast kopiować podstawowe dane.
Bezpośrednia komunikacja modelu danych
Możesz również ułatwić komunikację między wieloma wątkami bezpośrednio za pomocą modelu danych, w którym różni aktorzy mogą pisać i następnie czytać właściwości lub atrybuty.Jednakże, aby zachować bezpieczeństwo wątku, skrypty uruchamiane równolegle generalnie nie mogą pisać do modelu danych.Więc bezpośrednie korzystanie z modelu danych do komunikacji wiąże się z ograniczeniami i może zmusić skrypty do częstego synchronizowania, co może wpłynąć na wydajność skryptów.
Przykłady
Weryfikacja strony serwera raycastingu
Aby uzyskać doświadczenie walki i bitwy, musisz włączyć promieniowanie dla broni użytkowników.Z klientem symulującym broń, aby osiągnąć dobrą opóźnienie, serwer musi potwierdzić trafienie, które obejmuje wykonywanie rzutów i pewną ilość heurystyk, które obliczają prędkość oczekiwanego znaku, i patrzą na poprzednie zachowanie.
Zamiast używać pojedynczego scentralizowanego skryptu, który łączy się z zdarzeniem zdalnym, z którego korzystają klienci, aby komunikować informacje o trafieniu, możesz uruchomić każdy proces walidacji trafienia po stronie serwera równolegle z każdą postacią użytkownika mającą oddzielne zdarzenie zdalne.
Skrypt stronny uruchamiany pod tym postacią Actor łączy się z tym zdarzeniem zdalnym za pomocą równoległego połączenia, aby uruchomić odpowiednią logikę potwierdzającą trafienie.Jeśli logika znajdzie potwierdzenie trafienia, obrażenia są odliczane, co wiąże się z zmianą właściwości, więc początkowo uruchamia się seriowo.
local Workspace = game:GetService("Workspace")
local tool = script.Parent.Parent
local remoteEvent = Instance.new("RemoteEvent") -- Stwórz nowe zdalne wydarzenie i powiąż je z narzędziem
remoteEvent.Name = "RemoteMouseEvent" -- Zmień nazwę, aby lokalny skrypt mógł go wyszukać
remoteEvent.Parent = tool
local remoteEventConnection -- Utwórz odniesienie dla połączenia zdalnego zdarzenia
-- Funkcja, która słucha zdarzenia zdalnego
local function onRemoteMouseEvent(player: Player, clickLocation: CFrame)
-- SERIAL: Wykonaj kod konfiguracyjny w trybie szeregowym
local character = player.Character
-- Ignoruj charakter użytkownika podczas wysyłania promieni
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { character }
-- PARALELNE: Wykonaj rzut promieni w parze
task.desynchronize()
local origin = tool.Handle.CFrame.Position
local epsilon = 0.01 -- Używany do rozszerzenia promienia o kilka milimetrów, ponieważ położenie kliknięcia może być nieco przesunięte od obiektu
local lookDirection = (1 + epsilon) * (clickLocation.Position - origin)
local raycastResult = Workspace:Raycast(origin, lookDirection, params)
if raycastResult then
local hitPart = raycastResult.Instance
if hitPart and hitPart.Name == "block" then
local explosion = Instance.new("Explosion")
-- SERIAL: Kod poniżej modyfikuje stan poza aktorem
task.synchronize()
explosion.DestroyJointRadiusPercent = 0 -- Zrób wybuch niesmiertelnym
explosion.Position = clickLocation.Position
-- Wielu aktorów może uzyskać tę samą część w raycast i zdecydować się na jej zniszczenie
-- Jest to całkowicie bezpieczne, ale spowodowałoby to dwie eksplozje naraz zamiast jednej
-- Następujące podwójne sprawdzenia, że wykonanie dotarło do tej części najpierw
if hitPart.Parent then
explosion.Parent = Workspace
hitPart:Destroy() -- Zniszcz go
end
end
end
end
-- Połącz sygnał w trybie szeregowym na początku, ponieważ niektóry kod konfiguracyjny nie jest w stanie działać równolegle
remoteEventConnection = remoteEvent.OnServerEvent:Connect(onRemoteMouseEvent)
Generowanie terenu proceduralnego po stronie serwera
Aby stworzyć ogromny świat dla swojego doświadczenia, możesz wypełniać świat dynamicznie.Generacja proceduralna zwykle tworzy niezależne kawałki terenu, z generatorem wykonującym stosunkowo skomplikowane obliczenia dla umieszczenia obiektu, wykorzystania materiałów i wypełniania voxeli.Wykonanie kodu generacji równolegle może zwiększyć wydajność procesu.Poniższy przykład kodu służy jako przykład.
-- Wykonanie równoległe wymaga użycia aktorów
-- Ten skrypt klonuje się; oryginał inicjuje proces, podczas gdy klony działają jako robotnicy
local Workspace = game:GetService("Workspace")
local actor = script:GetActor()
if actor == nil then
local workers = {}
for i = 1, 32 do
local actor = Instance.new("Actor")
script:Clone().Parent = actor
table.insert(workers, actor)
end
-- Rodzic wszystkich aktorów pod samym sobą
for _, actor in workers do
actor.Parent = script
end
-- Instruuj aktorów, aby generowali teren, wysyłając wiadomości
-- W tym przykładzie aktorzy są wybierani losowo
task.defer(function()
local rand = Random.new()
local seed = rand:NextNumber()
local sz = 10
for x = -sz, sz do
for y = -sz, sz do
for z = -sz, sz do
workers[rand:NextInteger(1, #workers)]:SendMessage("GenerateChunk", x, y, z, seed)
end
end
end
end)
-- Wyjście z oryginalnego skryptu; reszta kodu uruchamiana jest w każdym aktorze
return
end
function makeNdArray(numDim, size, elemValue)
if numDim == 0 then
return elemValue
end
local result = {}
for i = 1, size do
result[i] = makeNdArray(numDim - 1, size, elemValue)
end
return result
end
function generateVoxelsWithSeed(xd, yd, zd, seed)
local matEnums = {Enum.Material.CrackedLava, Enum.Material.Basalt, Enum.Material.Asphalt}
local materials = makeNdArray(3, 4, Enum.Material.CrackedLava)
local occupancy = makeNdArray(3, 4, 1)
local rand = Random.new()
for x = 0, 3 do
for y = 0, 3 do
for z = 0, 3 do
occupancy[x + 1][y + 1][z + 1] = math.noise(xd + 0.25 * x, yd + 0.25 * y, zd + 0.25 * z)
materials[x + 1][y + 1][z + 1] = matEnums[rand:NextInteger(1, #matEnums)]
end
end
end
return {materials = materials, occupancy = occupancy}
end
-- Zwiąż powrotną funkcję do wywołania w kontekście wykonania równoległego
actor:BindToMessageParallel("GenerateChunk", function(x, y, z, seed)
local voxels = generateVoxelsWithSeed(x, y, z, seed)
local corner = Vector3.new(x * 16, y * 16, z * 16)
-- Obecnie WriteVoxels() musi być wezwany w fazie szeregowej
task.synchronize()
Workspace.Terrain:WriteVoxels(
Region3.new(corner, corner + Vector3.new(16, 16, 16)),
4,
voxels.materials,
voxels.occupancy
)
end)
Najlepsze praktyki
Aby zastosować maksymalne korzyści z programowania równoległego, odwołaj się do następujących najlepszych praktyk podczas dodawania kodu Luau:
Unikaj długich obliczeń — Nawet w paralelu długie obliczenia mogą blokować wykonanie innych skryptów i powodować lag.Unikaj używania programowania równoległego do obsługi dużej ilości długich, niezłomnych obliczeń.
Użyj właściwej liczby aktorów — Dla najlepszych wykonywanieużyj więcej Actors.Nawet jeśli urządzenie ma mniej rdzeni niż Actors, granularność pozwala na bardziej wydajne rozdzielanie obciążenia między rdzeniami.
Nie oznacza to, że powinieneś używać tak wielu Actors ile to możliwe.Powinieneś nadal dzielić kod na Actors w oparciu o jednostki logiki, zamiast łamać kod z połączoną logiką do różnych Actors.Na przykład, jeśli chcesz włączyć weryfikację raycastingu równolegle, rozsądne jest użycie 64 i więcej zamiast tylko 4, nawet jeśli celujesz w systemy 4-rdzeniowe.Jest to wartościowe dla skalowalności systemu i pozwala mu rozdzielać pracę w oparciu o możliwości podstawowego sprzętu.Jednak nie powinieneś używać zbyt wielu Actors, które są trudne do utrzymania.