병렬 Luau 프로그래밍 모델로 여러 스레드에서 코드를 동시에 실행할 수 있으므로 경험의 성능을 향상시킬 수 있습니다. 더 많은 콘텐츠로 경험을 확장함에 따라 이 모델을 채택하여 Luau 스크립트의 성능과 안전성을 유지할 수 있습니다.
병렬 프로그래밍 모델
기본적으로 스크립트는 순차적으로 실행됩니다. 경험에 플레이어가 아닌 캐릭터 (NPC), 레이캐스팅 유효성 검사 및 절차 생성과 같은 복잡한 로직이나 콘텐츠가 있는 경우, 순차 실행으로 인해 사용자에게 지연이 발생할 수 있습
병렬 프로그래밍 모델은 코드에 보안 혜택을 추가합니다. 코드를 여러 스레드로 분할하여 한 스레드에서 코드를 편집하면 병렬로 실행되는 다른 코드에는 영향을 주지 않습니다. 이렇게 하면 코드에 버그가 하나 있으면 전체 경험이 손상될 위험이 줄어들며, 업데이트
병렬 프로그래밍 모델을 채택하는 것은 모든 것을 여러 스레드에 넣는 것을 의미하지 않습니다. 예를 들어, 서버 측 레이캐스팅 유효성 검사는 각 개별 사용자를 병렬로 원격 이벤트로 설정하지만, 병렬 실행의 일반적인 패턴인 글로벌 속성을 변경하기 위해
대부분의 경우 원하는 출력을 달성하기 위해 직렬 및 병렬 단계를 결합해야 합니다, 현재 병렬로 지원되지 않는 일부 작업은 병렬 단계에서 인스턴스를 수정하는 것과 같이 스크립트 실행을 방지 할 수 있기 때문입니다. 병렬 API의 사용 수준에 대한 자세한 내용은 스레드 안전 을
코드를 여러 스레드로 나누기
경험의 스크립트를 여러 스레드에서 동시에 실행하려면 데이터 모델의 다른 액터 아래에서 논리적 청크로 분할해야 합니다. 액터는 Class.DataModel 에서 상속된 Actor 인스턴스로 표시됩니다. 이들은 동시
액터 인스턴스 배치
액터를 적절한 컨테이너에 넣거나 NPC 및 레이캐스터와 같은 3D 객체의 최상위 인스턴스 유형을 교체하고 해당 스크립트를 추가하는 경우 해당 스크립트를 참조하십시오. 스크립트.
대부분의 경우, 데이터 모델에서 액터를 다른 액터의 자식으로 두면 안됩니다. 그러나 특정 사용 사례에 대해 여러 액터에 중첩된 스크립트를 배치하기로 결정하면 스크립트는 가장 가까운 조상 액터가 소유합니다.
스레드 비동기화
스크립트를 액터 아래에 두면 병렬 실행이 가능하지만, 기본적으로 코드는 여전히 단일 스레드에서 직렬로 실행되므로 런타임 이행향상되지 않습니다. 코드를 병렬로 실행하기 위해 현재 코루틴의 실행
또한, 신호 콜백을 예약할 때 RBXScriptSignal:ConnectParallel() 메서드를 사용하여 트리거에 즉시 코드를 병렬로 실행할 수 있습니다. 신호 콜백 내에서 task.desynchronize()를 호출할 필요가 없습니다.
스레드 비동기화
local RunService = game:GetService("RunService")
RunService.Heartbeat:ConnectParallel(function()
... -- 상태 업데이트를 계산하는 일부 병렬 코드
task.synchronize()
... -- 인스턴스의 상태를 변경하는 일부 직렬 코드
end)
같은 액터의 일부인 스크립트는 항상 서로 관련하여 순차적으로 실행되므로 여러 액터가 필요합니다. 예를 들어, NPC의 모든 병렬 사용 가능 동작 스크립트를 한 액터에 넣으면 여전히 단일 스레드에서 직렬로 실행되지만, 다른 NPC 로직에 대한 여러
스레드 안전
병렬 실행 중에 DataModel 계층의 대부분의 인스턴스에 평소처럼 액세스 할 수 있지만 일부 API 속성 및 함수는 읽거나 쓰기에 안전하지 않습니다. 병렬 코드에서 사용하는 경우 Roblox 엔진은 이러한 액세스가 발생하는 것을 자동으로 감지하고 방지 할 수 있습니다.
다음 표에서 볼 수 있듯이 API 멤버에는 병렬 코드에서 사용할 수 있는지 여부와 방법을 나타내는 스레드 안전 수준이 있습니다.
안전 수준 | 속성용 | 함수용 |
---|---|---|
안전하지 않음 | 병렬로 읽거나 쓸 수 없습니다. | 병렬로 호출할 수 없습니다. |
병렬 읽기 | 병렬로 읽을 수는 있지만 쓸 수는 없습니다. | N/A |
로컬 금고 | 같은 액터에서 사용할 수 있습니다. 다른 Class.Actor|Actors가 병렬로 읽을 수는 있지만 쓰기는 불가능합니다. | 같은 액터에서 호출할 수 있습니다; 다른 액터에서 동시에 호출할 수 없습니다. |
금고 | 읽고 쓸 수 있습니다. | 호출할 수 있습니다. |
API 멤버에 대한 스레드 안전 태그를 API 참조에 찾을 수 있습니다. 이 태그를 사용할 때, API 호출이나 속성 변경이 병렬 스레드 사이에서 어떻게 상호 작용할 수 있는지 고려해야 합니다. 일반적으로 여러 액터가 다른 액터와 동일한 데이터를 읽지만 다른 액터의 상태를 수정하지 않는
교차 스레드 통신
멀티 스레드 컨텍스트에서는 다른 액터의 스크립트가 여전히 서로 통신하여 데이터를 교환하고, 작업을 조정하고, 활동을 동기화할 수 있습니다. 엔진은 교차 스레드 통신을 위해 다음과 같은 메커니즘을 지원합니다.
- 액터 메시징 API를 사용하여 스크립트를 사용하여 액터에게 메시지를 보내는 API.
- 공유 테이블 데이터 구조로, 공유 상태의 여러 액터 간에 대량의 데이터를 효율적으로 공유합니다.
교차 스레드 통신 요구를 수용하기 위해 여러 메커니즘을 지원할 수 있습니다. 예를 들어, 액터 메시징 API를 통해 공유 테이블을 보낼 수 있습니다.
액터 메시징
액터 메시징 API을 사용하면 직렬 또는 병렬 컨텍스트의 스크립트에서 동일한 데이터 모델의 액터에 데이터를 보낼 수 있습니다. 이 API를 통한 통신은 비동기적이며, 송신자는 수신자가 메시지를 수신할 때까지 차단하지 않습니다.
이 API를 사용하여 메시지를 보낼 때 주제 를 정의해야 합니다. 각 메시지는 단일 액터에게만 보낼 수 있지만, 그 액터는 내부적으로 메시지에 바인딩된 여러 개의 콜백을 가질 수 있습니다. 액터의 후손인 스크립트만 메시지를 수신할 수 있습니다.
API에는 다음과 같은 메서드가 있습니다.
- Actor:SendMessage() 액터에게 메시지 보내기.
- Actor:BindToMessage()로 Luau 콜백을 직렬 컨텍스트에서 지정한 토픽을 가진 메시지에 바인딩합니다.
- Actor:BindToMessageParallel() 루아우 콜백을 병렬 컨텍스트에서 지정한 토픽을 가진 메시지에 바인딩합니다.
다음 예시에서는 Actor:SendMessage()를 사용하여 주제를 정의하고 발신자에게 메시지를 보내는 방법을 보여줍니다.
메시지 발신자 예시
-- 작업자 액터에게 "인사"라는 주제로 두 개의 메시지 보내기local workerActor = workspace.WorkerActorworkerActor:SendMessage("Greeting", "Hello World!")workerActor:SendMessage("Greeting", "Welcome")print("Sent messages")
다음 예시에서는 Actor:BindToMessageParallel() 를 사용하여 수신자 측의 병렬 컨텍스트에서 특정 주제에 대한 콜백을 바인딩하는 방법을 보여줍니다.
메시지 수신기 예시
-- 이 스크립트의 부모인 액터를 가져옵니다.
local actor = script:GetActor()
-- 인사 메시지 주제에 대한 콜백 바인딩
actor:BindToMessageParallel("Greeting", function(greetingString)
print(actor.Name, "-", greetingString)
end)
print("Bound to messages")
공유 테이블
SharedTable 은 여러 액터에서 실행되는 스크립트에서 액세스 가능한 테이블 같은 데이터 구조입니다. 예를 들어, 여러 스레드에서 공유된 상태를 필요로 하며 많은 양의 데이터가 관련된 상황에서 유용합니다. 여러 액터가 데이터 모델에 저장되지 않은 공통 월드 상태
공유 테이블을 다른 액터로 전송하면 데이터의 복사본이 생성되지 않습니다. 대신, 공유 테이블을 사용하면 여러 스크립트에서 동시에 안전하고 원자적인 업데이트를 할 수 있습니다. 한 액터가 공유 테이블을 업데이트할 때마다 모든 액터가 즉시 볼 수 있습니다. 공유 테이블은 기
직접 데이터 모델 통신
데이터 모델을 사용하여 여러 스레드 간의 통신을 직접 용이하게 할 수 있습니다. 이 모델에서는 다른 액터가 속성이나 특성을 작성하고 나중에 읽을 수 있습니다. 그러나 스레드 안전을 유지하기 위해 병렬로 실행되는 스크립트는 일반적으로 데이터 모델에 쓸
예시
서버 사이드 레이캐스팅 유효성 검사
전투 경험과 전투에 대한 전투 경험을 위해, 사용자의 무기에 레이캐스팅을 활성화해야 합니다. 클라이언트가 무기를 시뮬레이션하여 좋은 대기 시간을 달성하도록 서버가 히트를 확인해야 하며, 이는 레이캐스트와 캐릭터의 예상 속도에 대한 계산 및
클라이언트가 히트 정보를 전송하는 원격 이벤트에 연결하는 단일 중앙화된 스크립트를 사용하는 대신, 서버 측에서 각 히트 유효성 검사 프로세스를 실행할 수 있으며, 모든 사용자 캐릭터가 별도의 원격 이벤트를 가질 수 있습니다.
그 캐릭터의 Class.Actor 아래에서 실행되는 서버 사이드 스크립트는 병렬 연결 사용으로 이 원격 이벤트에 연결하여 히트 확인을 위한 관련 논리를 실행합니다. 논리가 히트 확인을 찾으면 피해가 공제되며, 이는 속성을 변경하는 것을 포함하므로 처음에는 직렬로 실행됩니다.
local tool = script.Parent.Parent
local remoteEvent = Instance.new("RemoteEvent") -- 새로운 원격 이벤트를 생성하고 도구에 부모로 지정
remoteEvent.Name = "RemoteMouseEvent" -- 로컬 스크립트가 찾을 수 있도록 이름을 변경하십시오
remoteEvent.Parent = tool
local remoteEventConnection -- 원격 이벤트 연결에 대한 참조 생성
-- 원격 이벤트를 수신하는 기능
local function onRemoteMouseEvent(player: Player, clickLocation: CFrame)
-- 서리얼: 서리얼에서 설정 코드 실행
local character = player.Character
-- 레이캐스팅 중에 사용자의 캐릭터를 무시하십시오
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { character }
-- 병렬: 레이캐스트를 병렬로 수행
task.desynchronize()
local origin = tool.Handle.CFrame.Position
local epsilon = 0.01 -- 클릭 위치가 개체에서 약간 멀리 있기 때문에 레이를 조금 확장하여 사용
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")
-- 서리얼: 아래 코드는 액터 외부의 상태를 수정합니다.
task.synchronize()
explosion.DestroyJointRadiusPercent = 0 -- 폭발을 비 치명적으로 만드십시오
explosion.Position = clickLocation.Position
-- 여러 액터가 레이캐스트에서 동일한 부분을 얻을 수 있으며 그것을 파괴하기로 결정할 수 있습니다.
-- 완전히 안전하지만 한 번에 두 개의 폭발을 일으킵니다.
-- 다음 두 검사는 실행이 먼저 이 부분에 도착했는지 확인합니다.
if hitPart.Parent then
explosion.Parent = workspace
hitPart:Destroy() -- 파괴하기
end
end
end
end
-- 일부 설정 코드가 병렬로 실행할 수 없기 때문에 신호를 직렬로 처음 연결하십시오.
remoteEventConnection = remoteEvent.OnServerEvent:Connect(onRemoteMouseEvent)
서버-사이드 절차적 지형 생성
경험을 위한 광대한 세계를 만들려면 세계를 동적으로 채울 수 있습니다. 절차적 생성은 일반적으로 독립적인 지형 덩어리를 생성하며, 생성기는 개체 배치, 재료 사용 및 복셀 채우기에 대한 상대적으로 복잡한 계산을 수행합니다. 생성 코드를 병렬로 실행하면 프로세스의
-- 병렬 실행에는 액터의 사용이 필요합니다
-- 이 스크립트는 자체 복제됩니다. 원래은 프로세스를 시작하지만, 클론은 작업자로 작동합니다.
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
-- 자식 아래에 있는 모든 액터를 부모로 지정
for _, actor in workers do
actor.Parent = script
end
-- 액터에게 지형을 생성하도록 메시지 전송
-- 이 예에서는 액터가 랜덤으로 선택됩니다
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)
-- 원래 스크립트에서 나가십시오. 나머지 코드는 각 액터에서 실행됩니다.
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
-- 병렬 실행 컨텍스트에서 호출할 콜백 바인딩
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)
-- 현재, WriteVoxels()은 직렬 단계에서 호출해야 합니다.
task.synchronize()
workspace.Terrain:WriteVoxels(
Region3.new(corner, corner + Vector3.new(16, 16, 16)),
4,
voxels.materials,
voxels.occupancy
)
end)
모범 사례
병렬 프로그래밍의 최대 이점을 적용하려면 Lua 코드를 추가할 때 다음 모범 사례를 참조하십시오:
긴 계산을 피하십시오. — 심지어 병렬로도 긴 계산은 다른 스크립트의 실행을 차단하고 지연을 일으킬 수 있습니다. 길고 멈추지 않는 계산을 처리하기 위해 병렬 프로그래밍을 사용하지 마십시오.