Dengan Luau Paralel Luau model pemrograman, Anda dapat mengeksekusan kode pada lebih dari satu thread sekaligus, yang dapat meningkatkan kinerja pengalaman Anda. Saat Anda mengekspansi pengalaman Anda dengan lebih banyak konten, Anda dapat mengadopsi model ini untuk membantu menjaga kinerja dan keamanan skrip Luau Anda.
Model Pemrograman Paralel
Secara default, skrip eksekusi berurutan. Jika pengalaman Anda memiliki logika atau konten yang rumit, seperti karakter non-pemain (NPC), validasi raycasting, dan generasi prosedural, maka eksekusi berurutan mungkin menyebabkan kelambatan bagi pengguna Anda. Dengan model pemrograman paralel, Anda dapat membagi tugas menjadi beberapa script dan mengeksekutakann
Model pemrograman paralel juga menambahkan manfaat keamanan untuk kode Anda. Dengan membagi kode menjadi beberapa subprocess, ketika Anda mengedit kode di subprocess satu, itu tidak meng影响其他 kode yang berjalan secara paralel. Ini mengurangi risiko memiliki satu bug dalam kode Anda yang mengganggu seluruh pengalaman, dan mengurangi ketertinggalan bagi pengguna di server live saat Anda menekan update.
Mengadopsi model pemrograman paralel tidak berarti menempatkan segalanya dalam beberapa thread. Misalnya, Server-side Raycasting Validation menetapkan setiap pengguna individual acara remote secara paralel tetapi masih mengharuskan kode awal untuk dijalankan secara seri untuk mengubah proporsi global, yang merupakan pola umum untuk eksekusi paralel.
Sebagian besar waktu Anda perlu menggabungkan fase seri dan paralel untuk mencapai hasil yang diinginkan, karena saat ini ada beberapa operasi yang tidak didukung secara paralel yang dapat mencegah eksekusi skrip, seperti mengubah instans dalam fase paralel. Untuk lebih banyak informasi tentang tingkat penggunaan API dalam paralel, lihat KeselamatanThread.
Membagi Kode Menjadi Beberapa Thread
Untuk mengeksekusi skrip pengalaman Anda dalam beberapa thread secara bersamaan, Anda perlu membaginya menjadi potongan logik di bawah berbagai aktor di model data . Aktor diwakili oleh Actor instans yang mewarisi dari 1> Class.DataModel1> . Mereka bekerja sebagai unit isolasi eksekus
Menempatkan Instansi Aktor
Anda dapat menempatkan aktor di wadah yang sesuai atau menggunakannya untuk mengganti jenis instans tingkat atas dari entitas 3D Anda seperti NPC dan raycasters, lalu tambahkan skrip yang sesuai.
Untuk kebanyakan situasi, Anda seharusnya tidak menempatkan aktor sebagai anak aktor lain di model data. Namun, jika Anda memutuskan untuk menempatkan skrip berantai di banyak aktor untuk kasus penggunaan spesifik Anda, skrip dimiliki oleh aktor leluhur terdekat.
Menghubungkan Thread
Meskipun menempatkan skrip di bawah aktor memberi mereka kemampuan untuk eksekusi paralel, secara default kode masih dijalankan di thread tunggal secara seri, yang tidak meningkatkan kinerja runtime. Anda perlu menelepon fungsi task.desynchronize(), yang menyuspend eksekusi coroutine saat ini untuk mengeksekkan
Alternatifnya, Anda dapat menggunakan metode RBXScriptSignal:ConnectParallel() ketika Anda ingin menjadwalkan panggilan sinyal untuk segera mengeksekusi kode Anda secara paralel setelah memicu. Anda tidak perlu menelepon task.desynchronize() di dalam panggilan sinyal.
Non-sinkronisasi Thread
local RunService = game:GetService("RunService")
RunService.Heartbeat:ConnectParallel(function()
... -- Beberapa kode paralel yang menghitung pembaruan update
task.synchronize()
... -- Beberapa kode seri yang mengubah status instans
end)
Skrip yang merupakan bagian dari aktor yang sama selalu dijalankan secara berurutan dengan satu sama lain, jadi Anda memerlukan beberapa aktor. Misalnya, jika Anda menempatkan semua naskah perilaku yang diaktifkan paralel untuk NPC Anda dalam satu aktor, mereka masih dijalankan secara seri di satu thread, tetapi jika Anda memiliki beberapa aktor untuk logika NPC
KeselamatanThread
Selama eksekusi paralel, Anda dapat mengakses kebanyakan instans dari DataModel hierarki seperti biasa, tetapi beberapa API property dan fungsi tidak aman untuk dibaca atau ditulis. Jika Anda menggunakannya dalam kode paralel Anda, mesin Roblox dapat secara otomatis mendeteksi dan menghindari jenis akses ini terjadi.
Anggota API memiliki tingkat keamanan subprocess yang menunjukkan apakah dan bagaimana Anda dapat menggunakannya dalam kode paralel Anda, seperti yang ditunjukkan oleh tabel berikut:
Tingkat Keamanan | Untuk Properti | Untuk Fungsi |
---|---|---|
Tidak Aman | Tidak dapat dibaca atau ditulis secara paralel. | Tidak dapat dipanggil secara paralel. |
Baca Paralel | Dapat dibaca tetapi tidak ditulis secara paralel. | Tidak ada |
Dompet Lokal | Dapat digunakan dalam Actor yang sama; dapat dibaca tetapi tidak dapat ditulis oleh Actors lainnya secara paralel. | Dapat dipanggil dalam waktu yang sama Actor; tidak dapat dipanggil oleh Actors lainnya secara paralel. |
Aman | Dapat dibaca dan ditulis. | Bisa dipanggil. |
Anda dapat menemukan label keamanan subprocess untuk anggota API di referensi API. Saat menggunakannya, Anda juga harus mempertimbangkan cara panggilan API atau perubahan properti mungkin berinteraksi di antara subprocess paralel. Biasanya aman bagi beberapa aktor untuk membaca data yang sama dengan aktor lain tetapi tidak mengubah status dari aktor lain.
Komunikasi Antara Subprocess
Dalam konteks multithreading, Anda masih dapat mengizinkan skrip di berbagai aktor untuk berkomunikasi satu sama lain untuk menukar data, menkoordinasikan tugas, dan menyinkronisasi aktivitas. Engine mendukung berbagai mekanisme untuk komunikasi antithread:
- API untuk Mengirim Pesan ke Aktor Menggunakan Skrip.
- Tabel Berbagi data struktur untuk berbagi jumlah besar data di antara beberapa aktor dalam negara bagian bersama.
- Komunikasi Model Data Langsung untuk komunikasi sederhana dengan keterbatasan.
Anda dapat mendukung beberapa mekanisme untuk menangani kebutuhan komunikasi antara sub-Thread Anda. Misalnya, Anda dapat mengirim tabel bersama melalui Actor Messaging API.
Pesan Aktor
Aktor Messaging API mengizinkan script, baik dalam konteks seri atau paralel, untuk mengirim data ke aktor dalam model data yang sama. Komunikasi melalui API ini adalah asinkron, di mana pengirim tidak memblokir sampai penerima menerima pesan.
Saat mengirim pesan menggunakan API ini, Anda perlu mendefinisikan topik untuk kategori pesan untuk mengkategorikan pesan. Setiap pesan hanya dapat dikirim ke satu aktor, tetapi aktor itu dapat secara internal memiliki beberapa panggilan yang terikat pada pesan. Hanya skrip yang merupakan pendahulu aktor dapat menerima pesan.
API memiliki metode berikut:
- Actor:SendMessage() untuk mengirim pesan ke aktor.
- Actor:BindToMessage() untuk menyambungkan panggilan Luau ke pesan dengan topik yang ditentukan dalam konteks seri.
- Actor:BindToMessageParallel() untuk menyambungkan panggilan Luau ke pesan dengan topik yang ditentukan dalam konteks paralel.
Contoh berikut menunjukkan cara menggunakan Actor:SendMessage() untuk mendefinisikan topik dan mengirim pesan di akhiripengirim:
Pengirim Pesan Contoh
-- Kirim dua pesan ke aktor pekerja dengan topik "Pelukan"local workerActor = workspace.WorkerActorworkerActor:SendMessage("Greeting", "Hello World!")workerActor:SendMessage("Greeting", "Welcome")print("Sent messages")
Contoh berikut menunjukkan cara menggunakan Actor:BindToMessageParallel() untuk menyambungkan panggilan kembali untuk topik tertentu dalam konteks paralel pada akhiripenerima:
Penerima Pesan Contoh
-- Dapatkan aktor ke mana naskah ini dianggap
local actor = script:GetActor()
-- Ikat panggilan untuk topik pesan "Selamat Datang"
actor:BindToMessageParallel("Greeting", function(greetingString)
print(actor.Name, "-", greetingString)
end)
print("Bound to messages")
Tabel Berbagi
SharedTable adalah struktur data seperti tabel yang diakses dari skrip yang berjalan di bawah beberapa aktor. Ini berguna untuk situasi yang melibatkan banyak data dan memerlukan negara bagian bersama yang umum di antara beberapa thread. Misalnya, ketika beberapa aktor bekerja pada negara dunia yang tidak disimpan dalam model data.
Mengirim tabel shared ke aktor lain tidak membuat salinan data. Sebaliknya, tabel shared memungkinkan update yang aman dan atomik oleh banyak script secara bersamaan. Setiap update ke tabel shared oleh satu aktor langsung terlihat oleh semua aktor. Tabel shared juga dapat diklon dalam proses yang efisien secara资源, yang menggunakan shared sharing daripada mengkopi data yang berada di bawahnya.
Komunikasi Langsung Model Data
Anda juga dapat memudahkan komunikasi antara beberapa thread langsung menggunakan model data, di mana berbagai aktor dapat menulis dan kemudian membaca propperti atau属性. Namun, untuk menjaga keamanan thread, skrip yang dijalankan secara paralel umumnya tidak dapat menulis ke model data. Jadi, menggunakan langsung model data untuk komunikasi datang dengan keterbatasan dan dapat memaksa skrip untuk disinkronisasi deng
Contoh
Validasi Raycasting Sisi Server
Untuk pengalaman bertarung dan pertempuran, Anda perlu mengaktifkan raycasting untuk senjata pengguna Anda. Dengan klien menyimulasikan senjata untuk mencapai latensi yang baik, server harus mengkonfirmasi hit, yang melibatkan melakukan raycast dan beberapa jumlah heuristik yang menghitung kecepatan karakter yang diharapkan, dan melihat ke perilaku masa lalu.
Alih-alih menggunakan satu naskah pusat yang terhubung ke acara remote yang dikonsumsi klien untuk berkomunikasi dengan informasi hit, Anda dapat mengeksekusi setiap proses validasi hit di sisi server secara paralel dengan setiap karakter pengguna yang memiliki acara remote yang berpisah.
Skrip sisi server yang dijalankan di bawah karakter tersebut Actor terhubung ke acara remote ini menggunakan koneksi paralel untuk mengeksekusi logika yang relevan untuk mengkonfirmasi hit. Jika logika menemukan konfirmasi hit, kerusakan diambil, yang melibatkan mengubah prop, jadi itu dijalankan secara seri pada awalnya.
local tool = script.Parent.Parent
local remoteEvent = Instance.new("RemoteEvent") -- Buat acara remote baru dan isi alat
remoteEvent.Name = "RemoteMouseEvent" -- Ganti namanya agar lokasi skrip lokal dapat mencarinya
remoteEvent.Parent = tool
local remoteEventConnection -- Buat referensi untuk koneksi acara remote
-- Fungsi yang mendengarkan acara remote
local function onRemoteMouseEvent(player: Player, clickLocation: CFrame)
-- SERIAL: Eksekusikan kode pengaturan dalam serial
local character = player.Character
-- Abaikan karakter pengguna saat raycasting
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { character }
-- PARALEL: Lakukan raycast secara paralel
task.desynchronize()
local origin = tool.Handle.CFrame.Position
local epsilon = 0.01 -- Digunakan untuk menambahkan sedikit sinar karena lokasi klik mungkin sedikit berbeda dari objek
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: Kode di bawah ini mengubah status di luar aktor
task.synchronize()
explosion.DestroyJointRadiusPercent = 0 -- Jadikan ledakan tidak mematikan
explosion.Position = clickLocation.Position
-- Beberapa aktor dapat mendapatkan bagian yang sama dalam raycast dan memutuskan untuk menghancurkannya
-- Ini benar-benar aman tetapi akan menyebabkan dua ledakan sekaligus bukan satu
-- Langkah berikut memeriksa dua kali apakah eksekusi mendapatkan bagian ini terlebih dahulu
if hitPart.Parent then
explosion.Parent = workspace
hitPart:Destroy() -- Hancurkan itu
end
end
end
end
-- Hubungkan sinyal secara seri pada awalnya karena beberapa kode pengaturan tidak dapat dijalankan secara paralel
remoteEventConnection = remoteEvent.OnServerEvent:Connect(onRemoteMouseEvent)
Pembuatan Tanah Prosedural Sisi Server
Untuk membuat dunia yang luas untuk pengalaman Anda, Anda dapat mengisi dunia secara dinamis. Pembuatan kode biasanya menghasilkan potongan tanah yang lebih kecil, dengan generator melakukan perhitungan yang relatif rumit untuk tempat penempatan objek, penggunaan bahan, dan pengisian voxel. Mengeksekusi kode pembuatan secara paralel dapat meningkatkan efisiensi proses. Berikut adalah contoh kode.
-- Eksekusi paralel memerlukan penggunaan aktor
-- Skrip ini mengkloning dirinya sendiri; yang asli menginisialisasi proses, sementara klon bertindak sebagai pekerja
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
-- Orang tua semua aktor di bawah diri sendiri
for _, actor in workers do
actor.Parent = script
end
-- Instruksi aktor untuk menghasilkan terreno dengan mengirim pesan
-- Dalam contoh ini, aktor dipilih secara acak
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)
-- Keluar dari script asli; sisa kode berjalan di setiap aktor
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
-- Ikat panggilan untuk dipanggil dalam konteks eksekusi paralel
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)
-- Saat ini, WriteVoxels() harus dipanggil dalam fase seri
task.synchronize()
workspace.Terrain:WriteVoxels(
Region3.new(corner, corner + Vector3.new(16, 16, 16)),
4,
voxels.materials,
voxels.occupancy
)
end)
Praktik Terbaik
Untuk menerapkan manfaat maksimal pemrograman paralel, mengacu pada praktik terbaik berikut saat menambahkan kode Lua Anda:
Hindari Hitungan Panjang — Bahkan dalam paralel, panjang hitungan dapat menghentikan eksekusi script lain dan menyebabkan kelambatan. Hindari menggunakan pemrograman paralel untuk menangani volume besar dari panjang pemrosesan yang tak terputus.