Z wykorzystaniem modelu programowania Równoległy Luau, możesz uruchamiać kod na wielu wątkach jednocześnie, co może poprawić wydajność Twojego doświadczenia. W miarę rozwijania swojego doświadczenia o bardziej rozbudowane treści, możesz przyjąć ten model, aby pomóc w utrzymaniu wydajności i bezpieczeństwa Twoich skryptów Luau.
Model programowania równoległego
Domyślnie skrypty wykonują się sekwencyjnie. Jeśli Twoje doświadczenie ma złożoną logikę lub zawartość, taką jak postacie niezależne (NPC), walidacja raycasting i generowanie proceduralne, wykonanie sekwencyjne może powodować opóźnienia dla Twoich 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 kod Twojego doświadczenia działa szybciej, co poprawia wrażenia użytkownika.
Model programowania równoległego dodaje również korzyści związane z bezpieczeństwem do Twojego kodu. Dzieląc kod na wiele wątków, gdy edytujesz kod w jednym wątku, nie wpływa to na inny kod działający równolegle. To zmniejsza ryzyko, że jeden błąd w Twoim kodzie zniszczy całe doświadczenie i minimalizuje opóźnienie dla użytkowników na serwerach na żywo, gdy wprowadzisz aktualizację.
Przyjęcie modelu programowania równoległego nie oznacza, że wszystko ma być umieszczone w wielu wątkach. Na przykład, walidacja raycasting po stronie serwera ustawia zdarzenie zdalne dla każdego indywidualnego użytkownika równolegle, ale wciąż wymaga, aby początkowy kod działał sekwencyjnie, aby zmienić właściwości globalne, co jest powszechnym wzorcem dla równoległego wykonania.
W większości przypadków musisz połączyć fazy sekwencyjne i równoległe, aby osiągnąć pożądany wynik, ponieważ obecnie istnieją operacje, które nie są wspierane w trybie równoległym, co może uniemożliwić uruchamianie skryptów, takie jak modyfikowanie instancji w fazach równoległych. Aby uzyskać więcej informacji na temat poziomu użycia API w trybie równoległym, zobacz bezpieczeństwo wątków.
Podziel kod na wiele wątków
Aby uruchomić skrypty swojego doświadczenia w wielu wątkach jednocześnie, musisz podzielić je na logiczne fragmenty w różnych aktorach w modelu danych. Aktorzy są reprezentowani przez instancje Actor dziedziczące z DataModel. Działają jako jednostki izolacji wykonawczej, które rozkładają obciążenie na wiele rdzeni działających jednocześnie.
Umieść instancje aktorów
Możesz umieszczać aktorów w odpowiednich kontenerach lub używać ich do zastąpienia instancji najwyższego poziomu Twoich jednostek 3D, takich jak NPC i raycasterzy, a następnie dodać odpowiednie skrypty.

W większości sytuacji nie powinieneś umieszczać aktora jako dziecka innego aktora w modelu danych. Jednak jeśli zdecydujesz się umieścić skrypt zagnieżdżony w wielu aktorach dla swojej konkretnej sytuacji, skrypt będzie należał do najbliższego przodka aktora.

Dezsynchronizuj wątki
Choć umieszczanie skryptów pod aktorami przyznaje im zdolność do równoległego wykonania, domyślnie kod wciąż działa na jednym wątku sekwencyjnie, co nie poprawia wydajności wykonania. Musisz wywołać task.desynchronize(), funkcję, którą można zawiesić, która wstrzymuje wykonanie bieżącej korutyny, aby uruchomić kod równolegle i wznawia go przy następnej okazji wykonania równoległego. Aby przełączyć skrypt z powrotem do wykonania sekwencyjnego, wywołaj task.synchronize().
Alternatywnie możesz użyć metody RBXScriptSignal:ConnectParallel(), gdy chcesz zaplanować wywołanie sygnału, aby natychmiast uruchomić swój kod równolegle po wyzwoleniu. Nie musisz wywoływać task.desynchronize() wewnątrz wywołania sygnału.
Dezsynchronizuj wątek
local RunService = game:GetService("RunService")
RunService.Heartbeat:ConnectParallel(function()
... -- Jakiś równoległy kod, który oblicza aktualizację stanu
task.synchronize()
... -- Jakiś kod sekwencyjny, który zmienia stan instancji
end)
Skrypty, które są częścią tego samego aktora, zawsze wykonują się sekwencyjnie względem siebie, dlatego potrzebujesz wielu aktorów. Na przykład, jeśli umieścisz wszystkie skrypty zachowań włączających równoległość dla swojego NPC w jednym aktorze, wciąż będą działać sekwencyjnie w jednym wątku, ale jeśli będziesz miał wielu aktorów dla różnych logik NPC, każdy z nich działa równolegle na swoim własnym wątku. Aby uzyskać więcej informacji, zobacz Najlepsze praktyki.


Bezpieczeństwo wątków
Podczas równoległego wykonania, możesz uzyskać dostęp do większości instancji hierarchii DataModel jak zwykle, ale niektóre właściwości i funkcje API nie są bezpieczne do odczytu lub zapisu. Jeśli użyjesz ich w swoim równoległym kodzie, silnik Roblox może automatycznie wykryć i zapobiec tym dostępom.
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ższa tabela:
| Poziom bezpieczeństwa | Dla właściwości | Dla funkcji |
|---|---|---|
| Niebezpieczne | Nie można odczytywać ani zapisywać równolegle. | Nie można wywołać równolegle. |
| Odczytanie równolegle | Można odczytywać, ale nie zapisywać równolegle. | N/D |
| Lokalnie bezpieczne | Można używać w tym samym aktorze; można odczytywać, ale nie można do niego zapisywać przez inne Aktorów równolegle. | Można wywołać w tym samym aktorze; nie można wywołać przez inne Aktorów równolegle. |
| Bezpieczne | Można odczytywać i zapisywać. | Można wywołać. |
Możesz znaleźć tagi bezpieczeństwa wątków dla członków API w referencji API. Podczas ich używania powinieneś również uwzględnić, jak wywołania API lub zmiany właściwości mogą wchodzić w interakcje pomiędzy równoległymi wątkami. Zwykle jest bezpieczne dla wielu aktorów odczytywać te same dane, jak inne akty, ale nie modyfikować stanu innych aktorów.
Komunikacja międzywątkowa
W kontekście wielowątkowym możesz nadal pozwalać skryptom w różnych aktorach komunikować się ze sobą, aby wymieniać dane, koordynować zadania i synchronizować działania. Silnik wspiera następujące mechanizmy komunikacji międzywątkowej:
- API wiadomości aktora do wysyłania wiadomości do aktora za pomocą skryptów.
- Struktura danych wspólnej tabeli do efektywnego dzielenia się dużą ilością danych między wieloma aktorami na wspólnym stanie.
- Bezpośrednia komunikacja z modelem danych do prostej komunikacji z ograniczeniami.
Możesz wspierać wiele mechanizmów, aby dostosować się do potrzeb komunikacji międzywątkowej. Na przykład, możesz wysłać wspólną tabelę za pomocą API wiadomości aktora.
Wiadomości aktora
API wiadomości aktora pozwala skryptowi, działającemu w kontekście sekwencyjnym lub równoległym, wysyłać dane do aktora w tym samym modelu danych. Komunikacja przez to API jest asynchroniczna, w której nadawca nie blokuje, aż odbiorca odbierze wiadomość.
Podczas wysyłania wiadomości za pomocą tego API, musisz zdefiniować temat do kategoryzowania wiadomości. Każda wiadomość może być wysyłana tylko do pojedynczego aktora, ale ten aktor może wewnętrznie mieć wiele wywołań powiązanych z wiadomością. Tylko skrypty, które są potomkami aktora, mogą odbierać wiadomości.
API ma następujące metody:
- Actor:SendMessage() do wysyłania wiadomości do aktora.
- Actor:BindToMessage() do powiązania wywołania backlight Luau z wiadomością o określonym temacie w kontekście sekwencyjnym.
- Actor:BindToMessageParallel() do powiązania wywołania backlight Luau z wiadomością o określonym temacie w kontekście równoległym.
Poniższy przykład pokazuje, jak używać Actor:SendMessage() do zdefiniowania tematu i wysyłania wiadomości po stronie nadawcy:
Przykład nadawcy wiadomościlocal Workspace = game:GetService("Workspace")-- Wyślij dwie wiadomości do aktora roboczego z tematem "Pozdrowienie"local workerActor = Workspace.WorkerActorworkerActor:SendMessage("Rozmowa", "Witaj świecie!")workerActor:SendMessage("Rozmowa", "Witamy")print("Wysłano wiadomości")
Poniższy przykład pokazuje, jak używać Actor:BindToMessageParallel() do powiązania wywołania zwrotnego do określonego tematu w kontekście równoległym po stronie odbiorcy:
Przykład odbiorcy wiadomości
-- Uzyskaj aktora, do którego ten skrypt jest przypisany
local actor = script:GetActor()
-- Powiąż wywołanie zwrotne dla tematu wiadomości "Rozmowa"
actor:BindToMessageParallel("Rozmowa", function(greetingString)
print(actor.Name, "-", greetingString)
end)
print("Powiązano z wiadomościami")
Wspólna tabela
SharedTable jest strukturą danych podobną do tabeli, dostępną z poziomu skryptów działających pod wieloma aktorami. Jest przydatna w sytuacjach, które obejmują dużą ilość danych i wymagają wspólnego stanu między wieloma wątkami. Na przykład, gdy wiele aktorów pracuje nad wspólnym stanem świata, który nie jest przechowywany w modelu danych.
Wysłanie wspólnej tabeli do innego aktora nie tworzy kopii danych. Zamiast tego wspólne tabele pozwalają na bezpieczne i atomowe aktualizacje przez wiele skryptów jednocześnie. Każda aktualizacja wspólnej tabeli przez jednego aktora jest natychmiast widoczna dla wszystkich aktorów. Wspólne tabele można również klonować w procesie efektywnym pod względem zasobów, który wykorzystuje dzielenie strukturalne zamiast kopiowania podstawowych danych.
Bezpośrednia komunikacja z modelem 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 odczytywać właściwości lub atrybuty. Jednak w celu utrzymania bezpieczeństwa wątków, skrypty działające równolegle generalnie nie mogą pisać do modelu danych. Dlatego bezpośrednie użycie modelu danych do komunikacji wiąże się z ograniczeniami i może zmusić skrypty do częstego synchronizowania się, co może wpłynąć na wydajność Twoich skryptów.
Przykłady
Walidacja raycasting po stronie serwera
Aby stworzyć doświadczenie walki i bitwy, musisz włączyć raycasting dla broni Twoich użytkowników. Przy symulacji broni przez klienta, aby osiągnąć dobrą latencję, serwer musi potwierdzić trafienie, co oznacza wykonywanie raycastów i pewną ilość heurystyki, która oblicza oczekiwaną prędkość postaci i obserwuje wcześniejsze zachowania.
Zamiast używać jednego zcentralizowanego skryptu, który łączy się z zdalnym zdarzeniem, którego klienci używają do komunikacji informacji o trafieniach, możesz uruchomić każdy proces walidacji trafienia po stronie serwera równolegle, z każdą postacią użytkownika mającą oddzielne zdalne zdarzenie.
Skrypt po stronie serwera, który działa pod Actor tej postaci, łączy się z tym zdalnym zdarzeniem z użyciem połączenia równoległego, aby uruchomić odpowiednią logikę potwierdzającą trafienie. Jeśli logika znajdzie potwierdzenie trafienia, obrażenia są odejmowane, co wiąże się ze zmianą właściwości, więc początkowo działa sekwencyjnie.
local Workspace = game:GetService("Workspace")
local tool = script.Parent.Parent
local remoteEvent = Instance.new("RemoteEvent") -- Twórz nowe zdarzenie zdalne i umieść je w narzędziu
remoteEvent.Name = "RemoteMouseEvent" -- Zmień nazwę, aby lokalny skrypt mógł je znaleźć
remoteEvent.Parent = tool
local remoteEventConnection -- Utwórz referencję dla połączenia zdarzenia zdalnego
-- Funkcja nasłuchująca dla zdarzenia zdalnego
local function onRemoteMouseEvent(player: Player, clickLocation: CFrame)
-- SEKWENCYJNIE: Wykonaj kod konfiguracyjny sekwencyjnie
local character = player.Character
-- Ignoruj postać użytkownika podczas raycastingu
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { character }
-- RÓWNOLEGLE: Wykonaj raycast równolegle
task.desynchronize()
local origin = tool.Handle.CFrame.Position
local epsilon = 0.01 -- Używane, aby nieco przedłużyć ray, ponieważ miejsce 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")
-- SEKWENCYJNIE: Kod poniżej modyfikuje stan na zewnątrz aktora
task.synchronize()
explosion.DestroyJointRadiusPercent = 0 -- Spraw, aby eksplozja była niegroźna
explosion.Position = clickLocation.Position
-- Wiele aktorów może otrzymać ten sam obiekt w raycast i zdecydować o jego zniszczeniu
-- To jest całkowicie bezpieczne, ale spowoduje dwie eksplozje jednocześnie zamiast jednej
-- Poniższe podwójne sprawdzenie, że wykonanie dotarło najpierw do tej części
if hitPart.Parent then
explosion.Parent = Workspace
hitPart:Destroy() -- Zniszcz go
end
end
end
end
-- Połącz sygnał sekwencyjnie na początku, ponieważ niektóre kody konfiguracyjne nie mogą być uruchamiane równolegle
remoteEventConnection = remoteEvent.OnServerEvent:Connect(onRemoteMouseEvent)
Proceduralne generowanie terenu po stronie serwera
Aby stworzyć rozległy świat dla swojego doświadczenia, możesz dynamicznie go zaludnić. Generowanie proceduralne zazwyczaj tworzy niezależne kawałki terenu, przy czym generator wykonuje względnie złożone obliczenia dotyczące umieszczania obiektów, użycia materiału i wypełniania wokseli. Uruchamianie kodu generacji równolegle może zwiększyć wydajność procesu. Poniższy przykład kodu służy jako przykład.
-- Równoległe wykonanie wymaga użycia aktorów
-- Ten skrypt klonuje sam siebie; oryginał inicjuje proces, podczas gdy klony działają jako pracownicy
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
-- Umieść wszystkie aktory pod sobą
for _, actor in workers do
actor.Parent = script
end
-- Poleć aktorom generować 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)
-- Wyjdź z oryginalnego skryptu; reszta kodu działa 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
-- Powiąż wywołanie zwrotne, aby było wywoływane w równoległym kontekście wykonania
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ć wywoływane w fazie sekwencyjnej
task.synchronize()
Workspace.Terrain:WriteVoxels(
Region3.new(corner, corner + Vector3.new(16, 16, 16)),
4,
voxels.materials,
voxels.occupancy
)
end)
Najlepsze praktyki
Aby w pełni wykorzystać zalety programowania równoległego, zapoznaj się z poniższymi najlepszymi praktykami podczas dodawania swojego kodu Luau:
Unikaj długich obliczeń — Nawet w trybie równoległym, długie obliczenia mogą blokować wykonanie innych skryptów i powodować opóźnienia. Unikaj używania programowania równoległego do obsługi dużej ilości długich, nieprzerywających obliczeń.

Używaj odpowiedniej liczby aktorów — Aby uzyskać najlepszą wydajność, używaj większej liczby Aktorów. Nawet jeśli urządzenie ma mniej rdzeni niż Aktorów, granularity pozwala na efektywniejsze równoważenie obciążenia między rdzeniami.

To nie oznacza, że powinieneś używać tylu Aktorów, ile to możliwe. Powinieneś nadal dzielić kod na Aktorów na podstawie jednostek logiki, a nie dzielić kod z połączoną logiką na różne Aktorów. Na przykład, jeśli chcesz włączyć walidację raycasting równolegle, rozsądne jest użycie 64 Aktorów i więcej zamiast tylko 4, nawet jeśli celujesz w systemy czterordzeniowe. To jest wartościowe dla skalowalności systemu i pozwala na rozkładanie pracy na podstawie możliwości sprzętu. Jednak nie powinieneś też używać zbyt wielu Aktorów, co jest trudne do utrzymania.