Równoległy Luau

*Ta zawartość została przetłumaczona przy użyciu narzędzi AI (w wersji beta) i może zawierać błędy. Aby wyświetlić tę stronę w języku angielskim, kliknij tutaj.

Z modelem programowania Równoległy Luau możesz uruchamiać kod na wielu wątkach jednocześnie, co może poprawić wydajność twojej gry. W miarę rozwoju gry z większą ilością 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 twoja gra ma złożoną logikę lub treści, takie jak postacie niezależne (NPC), walidacja raycastów i generacja proceduralna, to sekwencyjne wykonanie 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 twojej gry działa szybciej, co poprawia doświadczenia użytkowników.

Model programowania równoległego dodaje również korzyści bezpieczeństwa 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łą grę i minimalizuje opóźnienia dla użytkowników na serwerach na żywo, gdy wprowadzasz aktualizację.

Przyjęcie modelu programowania równoległego nie oznacza, że musisz umieszczać wszystko w wielu wątkach. Na przykład, walidacja raycastów po stronie serwera ustawia dla każdego użytkownika zdarzenie zdalne równolegle, ale nadal wymaga, aby początkowy kod działał sekwencyjnie, aby zmienić globalne właściwości, co jest powszechnym wzorcem dla wykonania równoległego.

Najczęściej musisz połączyć fazy sekwencyjne i równoległe, aby osiągnąć pożądany wynik, ponieważ obecnie istnieją pewne operacje, które nie są obsługiwane w trybie równoległym, co może uniemożliwić działanie 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 twojej gry w wielu wątkach jednocześnie, musisz podzielić je na logiczne fragmenty pod różnymi aktorami w modelu danych. Aktorzy są reprezentowani przez instancje Actor, które dziedziczą po DataModel. Działają jako jednostki izolacji wykonania, które rozdzielają obciążenie na wiele rdzeni działających jednocześnie.

Umieść instancje aktorów

Możesz umieścić aktorów w odpowiednich kontenerach lub użyć ich do zastąpienia typów instancji na najwyższym poziomie twoich obiektów 3D, takich jak NPC i raycasterzy, a następnie dodać odpowiednie skrypty.

Przykład skryptu pod aktorem

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 swojego konkretnego przypadku użycia, skrypt jest własnością najbliższego przodka aktora.

Drzewo aktorów i skryptów, które pokazuje, jak skrypt jest własnością najbliższego aktora

Desynchronizuj wątki

Chociaż umieszczanie skryptów pod aktorami daje im możliwość równoległego wykonania, domyślnie kod nadal działa na pojedynczym wątku sekwencyjnie, co nie poprawia wydajności czasu wykonania. Musisz wywołać task.desynchronize(), funkcję, która może być wstrzymana, 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 na wykonanie sekwencyjne, 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.

Desynchronizuj wątek
local RunService = game:GetService("RunService")
RunService.Heartbeat:ConnectParallel(function()
... -- Kod równoległy, który oblicza aktualizację stanu
task.synchronize()
... -- Kod sekwencyjny, który zmienia stan instancji
end)

Skrypty, które są częścią tego samego aktora, zawsze wykonują się sekwencyjnie względem siebie, więc potrzebujesz wielu aktorów. Na przykład, jeśli umieścisz wszystkie skrypty zachowań z włączoną równoległością dla swojego NPC w jednym aktorze, nadal będą działać sekwencyjnie na jednym wątku, ale jeśli masz wiele 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.

Równoległy kod w aktorach działających sekwencyjnie na jednym wątku
Równoległy kod w aktorach działających jednocześnie na wielu wątkach

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ństwaDla właściwościDla funkcji
NiebezpieczneNie można odczytywać ani zapisywać w równolegle.Nie można wywoływać w równolegle.
Odczyt równoległyMożna odczytywać, ale nie można zapisywać w równolegle.N/D
Bezpieczne lokalnieMożna używać w tym samym aktorze; można odczytywać, ale nie można zapisywać przez inne Aktorzy w równolegle.Można wywoływać w tym samym aktorze; nie można wywoływać przez inne Aktorzy w równolegle.
BezpieczneMożna odczytywać i zapisywać.Można wywoływać.

Możesz znaleźć tagi bezpieczeństwa wątków dla członków API w referencji API. Korzystając z nich, powinieneś również rozważyć, jak wywołania API lub zmiany właściwości mogą współdziałać między równoległymi wątkami. Zwykle jest bezpieczne, aby wiele aktorów odczytywało te same dane co inne aktory, ale nie modyfikowało stanu innych aktorów.

Komunikacja między wątkami

W kontekście wielowątkowości możesz nadal pozwolić skryptom w różnych aktorach komunikować się ze sobą, aby wymieniać dane, koordynować zadania i synchronizować działania. Silnik obsługuje następujące mechanizmy komunikacji między wątkami:

Możesz wspierać wiele mechanizmów, aby dostosować się do swoich potrzeb komunikacji między wątkami. Na przykład, możesz wysłać wspólną tabelę przez API wiadomości aktora.

Wiadomości aktora

API wiadomości aktora pozwala skryptowi, zarówno w kontekście sekwencyjnym, jak i 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 otrzyma wiadomość.

Podczas wysyłania wiadomości za pomocą tego API musisz zdefiniować temat do kategoryzacji wiadomości. Każda wiadomość może być wysyłana tylko do jednego aktora, ale ten aktor może wewnętrznie mieć wiele wywołań zwrotnych powiązanych z wiadomością. Tylko skrypty, które są potomkami aktora, mogą odbierać wiadomości.

API ma następujące metody:

Poniższy przykład pokazuje, jak użyć Actor:SendMessage(), aby zdefiniować temat i wysłać wiadomość po stronie nadawcy:

Przykład nadawcy wiadomości
local Workspace = game:GetService("Workspace")
-- Wyślij dwie wiadomości do aktora roboczego z tematem "Powitanie"
local workerActor = Workspace.WorkerActor
workerActor:SendMessage("Greeting", "Witaj Świecie!")
workerActor:SendMessage("Greeting", "Witamy")
print("Wysłano wiadomości")

Poniższy przykład pokazuje, jak użyć Actor:BindToMessageParallel(), aby powiązać wywołanie zwrotne dla 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 "Powitanie"
actor:BindToMessageParallel("Greeting", function(greetingString)
print(actor.Name, "-", greetingString)
end)
print("Powiązano z wiadomościami")

Wspólna tabela

SharedTable to struktura danych podobna do tabeli, dostępna z 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 mogą być również klonowane w procesie efektywnym pod względem zasobów, który wykorzystuje współdzielenie strukturalne zamiast kopiowania podstawowych danych.

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ą zapisywać i następnie odczytywać właściwości lub atrybuty. Jednak aby zachować bezpieczeństwo wątków, skrypty działające równolegle zazwyczaj nie mogą zapisywać 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, co może wpłynąć na wydajność twoich skryptów.

Przykłady

Walidacja raycastów po stronie serwera

Dla gry walki i bitwy musisz włączyć raycasting dla broni twoich użytkowników. Z klientem symulującym broń, aby osiągnąć dobrą latencję, serwer musi potwierdzić trafienie, co wiąże się z wykonywaniem raycastów i pewną ilością heurystyki, która oblicza oczekiwaną prędkość postaci i analizuje wcześniejsze zachowanie.

Zamiast używać jednego scentralizowanego skryptu, który łączy się z zdalnym zdarzeniem, z którego klienci korzystają do komunikacji informacji o trafieniach, możesz uruchomić każdy proces walidacji trafienia po stronie serwera równolegle, przy czym każda postać użytkownika ma osobne zdalne zdarzenie.

Skrypt po stronie serwera, który działa pod Actor tej postaci, łączy się z tym zdalnym zdarzeniem za pomocą 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") -- Utwórz nowe zdalne zdarzenie i przypisz je do narzędzia
remoteEvent.Name = "RemoteMouseEvent" -- Zmień nazwę, aby lokalny skrypt mógł je znaleźć
remoteEvent.Parent = tool
local remoteEventConnection -- Utwórz referencję dla połączenia zdalnego zdarzenia
-- Funkcja, która nasłuchuje na zdalne zdarzenie
local function onRemoteMouseEvent(player: Player, clickLocation: CFrame)
-- SEKWENCYJNE: 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ÓWNOLEGŁE: Wykonaj raycast równolegle
task.desynchronize()
local origin = tool.Handle.CFrame.Position
local epsilon = 0.01 -- Używane do nieznacznego wydłużenia promienia, ponieważ lokalizacja kliknięcia może być nieznacznie przesunięta 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")
-- SEKWENCYJNE: Kod poniżej modyfikuje stan poza aktorem
task.synchronize()
explosion.DestroyJointRadiusPercent = 0 -- Uczyń eksplozję niegroźną
explosion.Position = clickLocation.Position
-- Wiele aktorów mogłoby uzyskać ten sam element w raycast i zdecydować się go zniszczyć
-- To jest całkowicie bezpieczne, ale skutkowałoby dwoma eksplozjami jednocześnie zamiast jednej
-- Poniższe podwójne sprawdza, czy 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ą działać równolegle
remoteEventConnection = remoteEvent.OnServerEvent:Connect(onRemoteMouseEvent)

Proceduralna generacja terenu po stronie serwera

Aby stworzyć ogromny świat dla swojej gry, możesz dynamicznie zapełniać świat. Generacja proceduralna zazwyczaj tworzy niezależne kawałki terenu, przy czym generator wykonuje stosunkowo złożone obliczenia dotyczące umiejscowienia obiektów, użycia materiałów i wypełnienia wokseli. Uruchamianie kodu generacji równolegle może zwiększyć efektywność tego procesu. Poniższy przykład kodu służy jako przykład.

-- Równoległe wykonanie wymaga użycia aktorów
-- Ten skrypt klonuje 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
-- Przypisz wszystkie aktory do siebie
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 kontekście równoległego 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 maksymalnie wykorzystać korzyści z programowania równoległego, zapoznaj się z następującymi 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ń.

    Diagram ilustrujący, jak przeciążenie fazy wykonania równoległego może nadal powodować opóźnienia
  • Używaj odpowiedniej liczby aktorów — Dla najlepszej wydajności używaj więcej Aktorów. Nawet jeśli urządzenie ma mniej rdzeni niż Aktorzy, granularność pozwala na bardziej efektywne równoważenie obciążenia między rdzeniami.

    Demonstracja, jak użycie większej liczby aktorów równoważy obciążenie między rdzeniami

    To nie oznacza, że powinieneś używać tak wielu Aktorów, jak to możliwe. Powinieneś nadal dzielić kod na Aktorów na podstawie jednostek logiki, a nie łamać kod z połączoną logiką na różne Aktorów. Na przykład, jeśli chcesz włączyć walidację raycastów równolegle, rozsądnie jest użyć 64 Aktorów i więcej zamiast tylko 4, nawet jeśli celujesz w systemy 4-rdzeniowe. To jest cenne dla skalowalności systemu i pozwala na rozdzielenie pracy w oparciu o możliwości sprzętu. Jednak nie powinieneś również używać zbyt wielu Aktorów, które są trudne do utrzymania.

©2026 Roblox Corporation. Nazwa Roblox, logo Roblox oraz hasło „Powering Imagination” należą do naszych zarejestrowanych i niezarejestrowanych znaków towarowych na terenie Stanów Zjednoczonych oraz w innych krajach.