Luau Song Song Song

*Nội dung này được dịch bằng AI (Beta) và có thể có lỗi. Để xem trang này bằng tiếng Anh, hãy nhấp vào đây.

Với mô hình lập trình Luau Song Song Song, bạn có thể chạy mã trên nhiều luồng đồng thời, điều này có thể cải thiện hiệu suất của trò chơi của bạn. Khi bạn mở rộng trò chơi của mình với nhiều nội dung hơn, bạn có thể áp dụng mô hình này để giúp duy trì hiệu suất và độ an toàn của các kịch bản Luau của bạn.

Mô hình lập trình song song

Theo mặc định, các kịch bản thực thi tuần tự. Nếu trò chơi của bạn có logic hoặc nội dung phức tạp, chẳng hạn như nhân vật không phải người chơi (NPC), xác thực raycasting và tạo ra quy trình, thì việc thực thi tuần tự có thể gây lag cho người dùng của bạn. Với mô hình lập trình song song, bạn có thể chia nhỏ các tác vụ thành nhiều kịch bản và chạy chúng song song. Điều này giúp mã trò chơi của bạn chạy nhanh hơn, cải thiện trải nghiệm người dùng.

Mô hình lập trình song song cũng mang lại lợi ích về độ an toàn cho mã của bạn. Bằng cách chia mã thành nhiều luồng, khi bạn chỉnh sửa mã trong một luồng, nó không ảnh hưởng đến mã khác đang chạy song song. Điều này giảm thiểu rủi ro có một lỗi trong mã của bạn làm hỏng toàn bộ trò chơi và giảm thiểu độ trễ cho người dùng trên các máy chủ trực tiếp khi bạn đẩy một bản cập nhật.

Việc áp dụng mô hình lập trình song song không có nghĩa là đưa mọi thứ vào nhiều luồng. Ví dụ, xác thực raycasting phía máy chủ thiết lập mỗi người dùng riêng lẻ một sự kiện từ xa song song nhưng vẫn yêu cầu mã ban đầu chạy tuần tự để thay đổi các thuộc tính toàn cục, đây là một mẫu phổ biến cho việc thực thi song song.

Hầu hết thời gian bạn cần kết hợp các giai đoạn tuần tự và song song để đạt được đầu ra mong muốn của mình, vì hiện tại có một số thao tác không được hỗ trợ trong song song có thể ngăn cản các kịch bản chạy, chẳng hạn như sửa đổi các thể hiện trong các giai đoạn song song. Để biết thêm thông tin về mức độ sử dụng của các API trong song song, hãy xem độ an toàn của luồng.

Chia mã thành nhiều luồng

Để chạy các kịch bản của trò chơi của bạn trong nhiều luồng đồng thời, bạn cần chia chúng thành các khối hợp lý dưới các diễn viên khác nhau trong mô hình dữ liệu. Các diễn viên được đại diện bởi các thể hiện Actor kế thừa từ DataModel. Chúng hoạt động như các đơn vị cách ly thực thi phân phối tải trên nhiều lõi chạy đồng thời.

Đặt các thể hiện diễn viên

Bạn có thể đặt các diễn viên trong các container thích hợp hoặc sử dụng chúng để thay thế các loại thể hiện cấp cao nhất của các thực thể 3D của bạn như NPC và raycaster, sau đó thêm các kịch bản tương ứng.

Một ví dụ về một Kịch bản dưới một Diễn viên

Trong hầu hết các tình huống, bạn không nên đặt một diễn viên là con của một diễn viên khác trong mô hình dữ liệu. Tuy nhiên, nếu bạn quyết định đặt một kịch bản lồng trong nhiều diễn viên cho trường hợp sử dụng cụ thể của bạn, kịch bản đó thuộc về diễn viên tổ tiên gần nhất của nó.

Một cây diễn viên và kịch bản cho thấy cách một kịch bản thuộc về diễn viên gần nhất của nó

Phi đồng bộ hóa các luồng

Mặc dù việc đặt các kịch bản dưới các diễn viên cho phép chúng có khả năng thực thi song song, theo mặc định mã vẫn chạy trên một luồng đơn tuần tự, điều này không cải thiện hiệu suất thời gian chạy. Bạn cần gọi task.desynchronize(), một hàm có thể tạm dừng thực thi của coroutine hiện tại để chạy mã song song và tiếp tục nó tại cơ hội thực thi song song tiếp theo. Để chuyển một kịch bản trở lại thực thi tuần tự, hãy gọi task.synchronize().

Ngoài ra, bạn có thể sử dụng phương thức RBXScriptSignal:ConnectParallel() khi bạn muốn lên lịch một callback tín hiệu để ngay lập tức chạy mã của bạn trong song song khi được kích hoạt. Bạn không cần gọi task.desynchronize() bên trong callback tín hiệu.

Phi đồng bộ hóa một Luồng
local RunService = game:GetService("RunService")
RunService.Heartbeat:ConnectParallel(function()
... -- Một số mã song song tính toán cập nhật trạng thái
task.synchronize()
... -- Một số mã tuần tự thay đổi trạng thái của các thể hiện
end)

Các kịch bản là một phần của cùng một diễn viên luôn thực thi tuần tự với nhau, vì vậy bạn cần nhiều diễn viên. Ví dụ, nếu bạn đặt tất cả các kịch bản hành vi được kích hoạt song song cho NPC của bạn trong một diễn viên, chúng vẫn chạy tuần tự trên một luồng đơn, nhưng nếu bạn có nhiều diễn viên cho các logic NPC khác nhau, mỗi diễn viên trong số đó chạy song song trên luồng riêng của nó. Để biết thêm thông tin, hãy xem Các thực tiễn tốt nhất.

Mã song song trong các Diễn viên chạy tuần tự trong một luồng đơn
Mã song song trong các Diễn viên chạy đồng thời trong nhiều luồng

Độ an toàn của luồng

Trong quá trình thực thi song song, bạn có thể truy cập hầu hết các thể hiện của phân cấp DataModel như thường lệ, nhưng một số thuộc tính và hàm API không an toàn để đọc hoặc ghi. Nếu bạn sử dụng chúng trong mã song song của mình, động cơ Roblox có thể tự động phát hiện và ngăn chặn những truy cập này xảy ra.

Các thành viên API có một mức độ an toàn của luồng cho biết liệu và cách bạn có thể sử dụng chúng trong mã song song của mình, như bảng sau đây cho thấy:

Mức độ an toànĐối với thuộc tínhĐối với hàm
Không an toànKhông thể đọc hoặc ghi trong song song.Không thể gọi trong song song.
Đọc Song SongCó thể đọc nhưng không thể ghi trong song song.N/A
An toàn cục bộCó thể được sử dụng trong cùng một Diễn viên; có thể được đọc nhưng không thể ghi bởi các Actors khác trong song song.Có thể được gọi trong cùng một Diễn viên; không thể được gọi bởi các Actors khác trong song song.
An toànCó thể được đọc và ghi.Có thể được gọi.

Bạn có thể tìm thấy các thẻ độ an toàn của luồng cho các thành viên API trên tài liệu API. Khi sử dụng chúng, bạn cũng nên xem xét cách các cuộc gọi API hoặc thay đổi thuộc tính có thể tương tác giữa các luồng song song. Thông thường, việc nhiều diễn viên đọc cùng một dữ liệu như các diễn viên khác là an toàn nhưng không sửa đổi trạng thái của các diễn viên khác.

Giao tiếp giữa các luồng

Dưới ngữ cảnh đa luồng, bạn vẫn có thể cho phép các kịch bản trong các diễn viên khác nhau giao tiếp với nhau để trao đổi dữ liệu, phối hợp các tác vụ và đồng bộ hóa các hoạt động. Động cơ hỗ trợ các cơ chế sau cho giao tiếp giữa các luồng:

  • API nhắn tin diễn viên để gửi tin nhắn đến một diễn viên bằng cách sử dụng các kịch bản.
  • Cấu trúc dữ liệu bảng chia sẻ để chia sẻ hiệu quả một lượng lớn dữ liệu giữa nhiều diễn viên trên một trạng thái chia sẻ.
  • Giao tiếp mô hình dữ liệu trực tiếp cho giao tiếp đơn giản với các hạn chế.

Bạn có thể hỗ trợ nhiều cơ chế để đáp ứng nhu cầu giao tiếp giữa các luồng của bạn. Ví dụ, bạn có thể gửi một bảng chia sẻ thông qua API Nhắn tin Diễn viên.

Nhắn tin diễn viên

API nhắn tin diễn viên cho phép một kịch bản, trong ngữ cảnh tuần tự hoặc song song, gửi dữ liệu đến một diễn viên trong cùng một mô hình dữ liệu. Giao tiếp thông qua API này là không đồng bộ, trong đó người gửi không chặn cho đến khi người nhận nhận được tin nhắn.

Khi gửi tin nhắn bằng cách sử dụng API này, bạn cần định nghĩa một chủ đề để phân loại tin nhắn. Mỗi tin nhắn chỉ có thể được gửi đến một diễn viên duy nhất, nhưng diễn viên đó có thể nội bộ có nhiều callback được liên kết với một tin nhắn. Chỉ các kịch bản là con cháu của một diễn viên mới có thể nhận tin nhắn.

API có các phương thức sau:

  • Actor:SendMessage() để gửi một tin nhắn đến một diễn viên.
  • Actor:BindToMessage() để liên kết một callback Luau với một tin nhắn có chủ đề xác định trong ngữ cảnh tuần tự.
  • Actor:BindToMessageParallel() để liên kết một callback Luau với một tin nhắn có chủ đề xác định trong ngữ cảnh song song.

Ví dụ sau đây cho thấy cách sử dụng Actor:SendMessage() để định nghĩa một chủ đề và gửi một tin nhắn từ phía người gửi:

Ví dụ Gửi Tin nhắn
local Workspace = game:GetService("Workspace")
-- Gửi hai tin nhắn đến diễn viên công nhân với chủ đề "Chào"
local workerActor = Workspace.WorkerActor
workerActor:SendMessage("Chào", "Xin chào Thế giới!")
workerActor:SendMessage("Chào", "Chào mừng")
print("Đã gửi tin nhắn")

Ví dụ sau đây cho thấy cách sử dụng Actor:BindToMessageParallel() để liên kết một callback cho một chủ đề nhất định trong ngữ cảnh song song từ phía người nhận:

Ví dụ Nhận Tin nhắn
-- Lấy diễn viên mà kịch bản này được gán cho
local actor = script:GetActor()
-- Liên kết một callback cho chủ đề tin nhắn "Chào"
actor:BindToMessageParallel("Chào", function(greetingString)
print(actor.Name, "-", greetingString)
end)
print("Đã liên kết với tin nhắn")

Bảng chia sẻ

SharedTable là một cấu trúc dữ liệu giống như bảng có thể truy cập từ các kịch bản chạy dưới nhiều diễn viên. Nó hữu ích cho các tình huống liên quan đến một lượng lớn dữ liệu và yêu cầu một trạng thái chia sẻ chung giữa nhiều luồng. Ví dụ, khi nhiều diễn viên làm việc trên một trạng thái thế giới chung không được lưu trữ trong mô hình dữ liệu.

Gửi một bảng chia sẻ đến một diễn viên khác không tạo ra một bản sao của dữ liệu. Thay vào đó, các bảng chia sẻ cho phép cập nhật an toàn và nguyên tử bởi nhiều kịch bản đồng thời. Mỗi cập nhật cho một bảng chia sẻ bởi một diễn viên sẽ ngay lập tức được nhìn thấy bởi tất cả các diễn viên. Các bảng chia sẻ cũng có thể được sao chép trong một quy trình tiết kiệm tài nguyên sử dụng chia sẻ cấu trúc thay vì sao chép dữ liệu cơ bản.

Giao tiếp mô hình dữ liệu trực tiếp

Bạn cũng có thể tạo điều kiện giao tiếp giữa nhiều luồng trực tiếp bằng cách sử dụng mô hình dữ liệu, trong đó các diễn viên khác nhau có thể ghi và sau đó đọc các thuộc tính hoặc thuộc tính. Tuy nhiên, để duy trì độ an toàn của luồng, các kịch bản chạy trong song song thường không thể ghi vào mô hình dữ liệu. Vì vậy, việc sử dụng trực tiếp mô hình dữ liệu cho giao tiếp đi kèm với các hạn chế và có thể buộc các kịch bản phải đồng bộ hóa thường xuyên, điều này có thể ảnh hưởng đến hiệu suất của các kịch bản của bạn.

Ví dụ

Xác thực raycasting phía máy chủ

Đối với một trò chơi chiến đấu và chiến tranh, bạn cần kích hoạt raycasting cho vũ khí của người dùng. Với khách hàng mô phỏng vũ khí để đạt được độ trễ tốt, máy chủ phải xác nhận cú đánh, điều này liên quan đến việc thực hiện raycasts và một số lượng heuristics tính toán vận tốc nhân vật mong đợi, và xem xét hành vi trong quá khứ.

Thay vì sử dụng một kịch bản trung tâm duy nhất kết nối với một sự kiện từ xa mà khách hàng sử dụng để giao tiếp thông tin cú đánh, bạn có thể chạy mỗi quy trình xác thực cú đánh trên phía máy chủ song song với mỗi nhân vật người dùng có một sự kiện từ xa riêng biệt.

Kịch bản phía máy chủ chạy dưới Actor của nhân vật đó kết nối với sự kiện từ xa này bằng cách sử dụng một kết nối song song để chạy logic liên quan để xác nhận cú đánh. Nếu logic tìm thấy một xác nhận cú đánh, thiệt hại sẽ được trừ đi, điều này liên quan đến việc thay đổi các thuộc tính, vì vậy nó chạy tuần tự ban đầu.

local Workspace = game:GetService("Workspace")
local tool = script.Parent.Parent
local remoteEvent = Instance.new("RemoteEvent") -- Tạo sự kiện từ xa mới và gán nó cho công cụ
remoteEvent.Name = "RemoteMouseEvent" -- Đổi tên để kịch bản cục bộ có thể tìm kiếm nó
remoteEvent.Parent = tool
local remoteEventConnection -- Tạo một tham chiếu cho kết nối sự kiện từ xa
-- Hàm lắng nghe một sự kiện từ xa
local function onRemoteMouseEvent(player: Player, clickLocation: CFrame)
-- TUẦN TỰ: Thực thi mã thiết lập trong tuần tự
local character = player.Character
-- Bỏ qua nhân vật của người dùng trong khi raycasting
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { character }
-- SONG SONG: Thực hiện raycast trong song song
task.desynchronize()
local origin = tool.Handle.CFrame.Position
local epsilon = 0.01 -- Sử dụng để mở rộng ray một chút vì vị trí nhấp có thể bị lệch một chút so với đối tượng
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")
-- TUẦN TỰ: Mã bên dưới thay đổi trạng thái bên ngoài diễn viên
task.synchronize()
explosion.DestroyJointRadiusPercent = 0 -- Làm cho vụ nổ không gây chết người
explosion.Position = clickLocation.Position
-- Nhiều diễn viên có thể nhận cùng một phần trong một raycast và quyết định phá hủy nó
-- Điều này hoàn toàn an toàn nhưng sẽ dẫn đến hai vụ nổ cùng một lúc thay vì một
-- Kiểm tra sau đây đảm bảo rằng thực thi đã đến phần này trước
if hitPart.Parent then
explosion.Parent = Workspace
hitPart:Destroy() -- Phá hủy nó
end
end
end
end
-- Kết nối tín hiệu trong tuần tự ban đầu vì một số mã thiết lập không thể chạy trong song song
remoteEventConnection = remoteEvent.OnServerEvent:Connect(onRemoteMouseEvent)

Tạo địa hình quy trình phía máy chủ

Để tạo ra một thế giới rộng lớn cho trò chơi của bạn, bạn có thể làm đầy thế giới một cách động. Tạo ra quy trình thường tạo ra các khối địa hình độc lập, với bộ tạo thực hiện các phép tính tương đối phức tạp cho việc đặt đối tượng, sử dụng vật liệu và lấp đầy voxel. Chạy mã tạo ra trong song song có thể nâng cao hiệu quả của quy trình. Mẫu mã sau đây phục vụ như một ví dụ.

-- Thực thi song song yêu cầu sử dụng các diễn viên
-- Kịch bản này sao chép chính nó; bản gốc khởi động quy trình, trong khi các bản sao hoạt động như công nhân
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
-- Gán tất cả các diễn viên dưới chính nó
for _, actor in workers do
actor.Parent = script
end
-- Hướng dẫn các diễn viên tạo địa hình bằng cách gửi tin nhắn
-- Trong ví dụ này, các diễn viên được chọn ngẫu nhiên
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)
-- Thoát khỏi kịch bản gốc; phần còn lại của mã chạy trong mỗi diễn viên
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
-- Liên kết callback để được gọi trong ngữ cảnh thực thi song song
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)
-- Hiện tại, WriteVoxels() phải được gọi trong giai đoạn tuần tự
task.synchronize()
Workspace.Terrain:WriteVoxels(
Region3.new(corner, corner + Vector3.new(16, 16, 16)),
4,
voxels.materials,
voxels.occupancy
)
end)

Các thực tiễn tốt nhất

Để áp dụng tối đa lợi ích của lập trình song song, hãy tham khảo các thực tiễn tốt nhất sau đây khi thêm mã Luau của bạn:

  • Tránh Tính toán Dài — Ngay cả trong song song, các tính toán dài có thể chặn việc thực thi của các kịch bản khác và gây lag. Tránh sử dụng lập trình song song để xử lý một khối lượng lớn các phép tính dài, không thể tạm dừng.

    Sơ đồ minh họa cách quá tải giai đoạn thực thi song song vẫn có thể gây lag
  • Sử dụng Số Lượng Diễn viên Phù hợp — Để có hiệu suất tốt nhất, hãy sử dụng nhiều Actors. Ngay cả khi thiết bị có ít lõi hơn Actors, độ chi tiết cho phép cân bằng tải hiệu quả hơn giữa các lõi.

    Minh họa cách sử dụng nhiều diễn viên cân bằng tải giữa các lõi

    Điều này không có nghĩa là bạn nên sử dụng càng nhiều Actors càng tốt. Bạn vẫn nên chia mã thành các Actors dựa trên các đơn vị logic thay vì phá vỡ mã với logic kết nối đến các Actors khác nhau. Ví dụ, nếu bạn muốn kích hoạt xác thực raycasting trong song song, hợp lý là sử dụng 64 Actors và nhiều hơn nữa thay vì chỉ 4, ngay cả khi bạn đang nhắm đến các hệ thống 4 lõi. Điều này có giá trị cho khả năng mở rộng của hệ thống và cho phép nó phân phối công việc dựa trên khả năng của phần cứng cơ bản. Tuy nhiên, bạn cũng không nên sử dụng quá nhiều Actors, điều này khó duy trì.

©2026 Roblox Corporation. Roblox, logo Roblox và Powering Imagination là các nhãn hiệu đã đăng ký và chưa đăng ký của chúng tôi tại Hoa Kỳ và các quốc gia khác.