Z modelem programowania Luau Parallel możesz uruchomić kod na wielu wątkach równocześnie, co może poprawić wydajność swojego doświadczenia. Gdy rozwiesz swoje doświadczenie z więcej treścią, możesz przyjąć ten model, aby pomóc utrzymać wydajność i bezpieczeństwo swoich luau skryptów.
Model programowania równoległego
Domyślnie skrypty wykonują się kolejno. Jeśli twoja wrażliwość ma złożoną logikę lub treść, taką jak nierozgrywające postacie (NPC), weryfikacja raycastingu i proceduralna generacja, to wykonanie kolejności może powodować opóźnienie dla twoich użytkowników. Z modelu programowania równoległego możesz podzielić zadania na wiele skryptów i wy
Model programowania równoległego dodaje również korzyści bezpieczeństwa do twojego kodu. Poprzez podzielenie kodu na wiele wątków, gdy edytujesz kod w jednym wątku, nie wpływa to na inne wątki, które są równolegle uruchomione. To zmniejsza ryzyko, że jeden błąd w twoim kodzie uniemożliwia całą eksperyencję i zmniejsza opóźnienie dla użytkowników na żywych serwerach, gdy wys
Adopting the parallel programming model doesn't mean to put everything in multiple threads. For example, the Server-side Raycasting Validation sets each individual user a remote event in parallel but still requires the initial code to run serially to change global properties, which is a common pattern for parallel execution.
W większości przypadków musisz połączyć fazę serwialną i równoległą, aby uzyskać pożądany wynik, ponieważ obecnie istnieje kilka operacji nieobsługiwanych równolegle, które mogą zapobiec uruchomieniu skryptów, takich jak modyfikowanie instancji w fazach równoległych. Dla więcej informacji o poziomie użycia API w równolekle, zobacz bezpieczeństwo wątku.
Dzielenie kodu na wiele wątków
Aby uruchomić skrypciny swojego doświadczenia w wielu wątkach równocześnie, musisz je podzielić na logiczne kawałki pod różnymi aktorami w modelu danych. Aktorzy są reprezentowane przez instancje Actor , które dziedziczą się z 2> Class.DataModel2>. Robią one jako jednostki izolacji
Umieszczanie instancji aktora
Możesz umieścić aktorów w odpowiednich kontenerach lub użyć ich do zastąpienia głównych typów instancji twoich 3D, takich jak NPC i raycastery, a następnie dodać odpowiednie skrypty.
W większości sytuacji nie powinieneś umieszczać aktora jako dziecka innego aktora w modelu danych. Jeśli jednak zdecydujesz się umieścić skrypt wielostronnie w różnych aktorach dla swojego konkretnego przypadku użycia, skrypt należą do najbliższego przodka aktora.
Desynchronizacja wątków
Chociaż umieszczenie skryptów pod aktorami nadaje im zdolność do równoległej eksekucji, domyślnie kod nadal jest uruchomiony na jednym wątku seriami, co nie poprawia wykonywanieczasu uruchomienia. Musisz zadzwonić funkcję task.desynchronize(), która zawiesza wykonanie bieżącej rutyny dla
Alternatywnie możesz użyć metody RBXScriptSignal:ConnectParallel(), aby zaplanować wezwanie sygnału, aby natychmiastowo wykonany swój kod w paralelu po włączeniu. Nie musisz wzywać task.desynchronize() w wezwaniu sygnału.
Odesynchronizuj wątek
local RunService = game:GetService("RunService")
RunService.Heartbeat:ConnectParallel(function()
... -- Kod równoległy, który wykonuje aktualizację aktualizacja
task.synchronize()
... -- Kod serweryjny, który zmienia stan instancji
end)
Skryпty, które są częścią tego samego aktora zawsze są wykonane kolejno względem siebie nawzajem, więc potrzebujesz wielu aktorów. Na przykład, jeśli wszystkie włączone skryпty zachowań dla twojego NPC są w jednym aktorze, nadal będą one działać seriami na jednym wątku, ale jeśli masz więcej aktorów dla różnych zachowań NPC,
Zabezpieczenie wątku
Podczas wykonania równoległego możesz uzyskać dostęp do większości instancji DataModel hierarchii zazwyczaj, ale niektóre właściwości i funkcje API nie są bezpieczne do czytania lub pisania. Jeśli użyjesz ich w swoim równolepnym kodzie, silnik Roblox może automatycznie wykryć i zapobiec, że dochodzi do tych dostępu.
Członkowie API mają poziom bezpieczeństwa wątku, który wskazuje, czy i jak można 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że być czytany lub zapisany równolegle. | Nie można go wezwywać równolegle. |
Czytaj równolegle | Można czytać, ale nie pisać równolegle. | Nie dotyczy |
Lokalny Safe | Można używać w tym samym aktorze; można czytać, ale nie zapisać przez innych Actors równolegle. | Można go wezwieć w tym samym aktorze; nie można go wezwieć przez innych Actors równocześnie. |
Skrzynka bezpieczeństwa | Można czytać i zapisać. | Można go wezwieć. |
Możesz znaleźć flagi bezpieczeństwa wątków dla członków API na odniesieniu API. Gdy je używasz, powinieneś również rozważyć, jak wezwania API lub zmiany właściwości mogą interaktywnie interagować między wątkami równoległymi. Zazwyczaj jest bezpiecznie, gdy kilku aktorów czyta tę samą samą dawkę danych, co inni aktorzy, ale nie modyfikuje stanu innych aktorów.
Komunikacja między wątkami
W kontekście wielowątkowości nadal możesz pozwolić skryptom w różnych aktorach na komunikację ze sobą, aby wymienić dane, koordynować zadania i synchronizować działania. Silnik wspiera następujące mechanizmy dla komunikacji między wątkami:
- 共享表 struktura danych do wydajnego dzielenia dużej ilości danych między różnymi aktorami w stanie współdzielonym.
- Direct Data Model Communication dla prostego komunikatu z ograniczeniami.
Możesz wspierać wiele mechanizmów, aby zaspokoić swoje potrzeby komunikacji między wątkami. Na przykład możesz wysłać współdzielony tabelę za pośrednictwem Actor Messaging API.
Messaging aktora
API Actor Messaging umożliwia wysyłanie danych do aktora w dowolnym modelu danych. Komunikacja za pośrednictwem tej API jest asynchronowa, w której nadawca nie blokuje, aż do otrzymania wiadomość.
Podczas wysyłania wiadomości za pomocą tej API musisz zdefiniować podmiot dla kategorii wiadomości. Każda wiadomość może być wysłana tylko do jednego aktora, ale ten aktor może mieć wewnętrznie wiele powiadomień związanych z wiadomością. Tylko skrypty, które są potomstwami aktora, mogą otrzymywać wiadomości.
API ma następujące metody:
- Actor:SendMessage() do wysyłania wiadomości do aktora.
- Actor:BindToMessage() dla wiązania zwrotu wzywania Luau z wiadomością z określonym tematem w kontekście serwera.
- Actor:BindToMessageParallel() dla wiązania zwrotu wzywania Luau z wiadomością z określonym tematem w równolepnym kontekście.
Poniższy przykład pokazuje, jak użyć Actor:SendMessage() , aby zdefiniować temat i wysłać wiadomość na kończyćnadawcy:
Wybrane Przesyłaczki
-- Wyślij dwa wiadomości dla aktora pracy 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 związać wiadomość zwrotną dla pewnego tematu w równolepnym kontekście na kończyćodbiorcy:
Odbiornik Działania Przykładu
-- Zdobądź aktora, do którego ten skrypt jest związany
local actor = script:GetActor()
-- Zwiąż funkcję zwrotną dla tematu "Powitanie"
actor:BindToMessageParallel("Greeting", function(greetingString)
print(actor.Name, "-", greetingString)
end)
print("Bound to messages")
Udostępniony Stół
SharedTable to struktura danych podobna do tabeli dostępna z krypt skryptów działających pod wieloma aktorami. Jest użyteczny dla sytuacji, które wiążą się z dużą ilością danych i wymagają 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 zapisany w modelu danych.
Wysyłanie współdzielonej tabeli do innego aktora nie tworzy kopii danych. Zamiast tego współdzielne tabeli umożliwiają bezpieczne i atomowe aktualizacje przez wiele skryptów jednocześnie. Każda aktualizacja współdzielonej tabeli przez jednego aktora jest natychmiastowo widoczna dla wszystkich aktorów. Tabela współdzielenia może również być klonowana w wydajnym procesie, który wykorzystuje dzielenie strukturalne zamiast k
Direct Data Model Komunikacji
Możesz również ułatwić komunikację między różnymi 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. Aby jednak utrzymać bezpieczeństwo wątku, skrypty będące w ruchu równolegle generalnie nie mogą pisać do modelu danych. Więc bezpośrednie używanie modelu danych dla komunikacji przychod
Przykłady
Weryfikacja Raycastingu stron serwera
Dla doświadczenia walki i bitwy musisz włączyć raycasting dla broni użytkowników. Z przy użyciem klienta symulującego broń, serwer musi potwierdzić trafienie, co wiąże się z wykonaniem raycastów i pewną ilości heurystyk, które obliczają oczekiwaną prędkość postaci i spoglądają na zachowanie przeszłości.
Zamiast używać pojedynczego z centralizowanym skryptem, który łączy się z zdalnym wydarzeniem, które klienty używają do komunikacji informacji o trafieniach, możesz wykonać każdy proces weryfikacji trafień na serwerze równocześnie z każdym użytkownikiem, który ma odrębne zdalne wydarzenie.
Skrypt stron serwera, które działają pod tym postacią Actor połączą się z tym zdalnym wydarzeniem za pośrednictwem połączenia równoległego, aby wykonać odpowiednią logikę potwierdzającą trafienie. Jeśli logika znajdzie potwierdzenie trafienia, zostanie odebrany obrażenia, co wiąże się ze zmianą właściwości, więc zostanie ona uruchomiona seriami początkowo.
local tool = script.Parent.Parent
local remoteEvent = Instance.new("RemoteEvent") -- Utwórz nowy zdalny wątek i przywiąż go do narzędzia
remoteEvent.Name = "RemoteMouseEvent" -- Zmień nazwę, aby lokalny skrypt mógł go szukać
remoteEvent.Parent = tool
local remoteEventConnection -- Utwórz odniesienie do zdalnej połączenia urządzenia
-- Funkcja słuchająca zdarzenia zdalnego
local function onRemoteMouseEvent(player: Player, clickLocation: CFrame)
-- SERIAL: Wykonaj kod ustawień w serwialu
local character = player.Character
-- Ignoruj postać użytkownika podczas rzucania promieniami
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { character }
-- PARALLEL: Wykonaj raycast w parze
task.desynchronize()
local origin = tool.Handle.CFrame.Position
local epsilon = 0.01 -- Używany do przedłużenia promienia o niewielkim, ponieważ lokalizacja kliknięcia może być nieco odchylona 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 zmienia stan poza aktorem
task.synchronize()
explosion.DestroyJointRadiusPercent = 0 -- Zrób eksplozję nieśmiertelną
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 dwie eksplozje jednocześnie zamiast jednej
-- Poniższe 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 serweryjnym początkowo, ponieważ niektórzy kod ustawień nie są w stanie działać równolegle
remoteEventConnection = remoteEvent.OnServerEvent:Connect(onRemoteMouseEvent)
Generacja terenu strony serwera
Aby stworzyć wielki świat dla twojego doświadczenia, możesz zaludnić świat dynamicznie. Generacja proceduralna zwykle tworzy niezależne kawałki terenu, z generatorem wykonującym stosunkowo skomplikowane obliczenia dla umieszczenia obiektów, użycia materiałów i wypełniania woxel. Wykonanie kodu generacji równolegle może poprawić wydajność procesu. Poniższy przykładowy kod służy jako przykład.
-- Równoległe wykonanie wymaga użycia aktorów
-- Ten skrypt kopiuje siebie; oryginał inicjuje proces, a kopie działają jako pracy
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
-- Rodzice wszystkich aktorów pod samym sobą
for _, actor in workers do
actor.Parent = script
end
-- Naucz aktorów 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 biegnie 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ąż funkcję, aby była ona wymierzona do równoległego kontekstu 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)
-- ObecnieWriteVoxels() musi zostać wezwany w fazie serweryjnej
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, zwróć uwagę na następujące najlepsze praktyki podczas dodawania kodu Lua:
Unikaj długich obliczeń — Nawet równolegle długie obliczenia mogą blokować wykonanie innych skryptów i powodować opóźnienia. Unikaj używania równoległej programowania, aby zarządzać dużą ilością długich, niezawieszonych obliczeń.