실행 제어

# 학습 과정 소개

클라이언트와 서버의 흐름을 쉽게 제어할 수 있게 하는 실행 제어의 개념과 활용 방법을 알아봅니다.

##### 들어가기에 앞서

본 과정을 이해하기 위해선 다음 가이드 선행 학습이 필요합니다. 

# 서버 - 클라이언트 간 데이터 통신

클라이언트 - 서버 간의 흐름을 제어하려면 데이터를 전송해야 합니다. 예를 들어, 서버에서 클라이언트로 "3초간 춤을 춰!"라고 요청하려면 어떤 것이 필요할까요?   
두 사람이 대화하는 상황으로 바꿔 생각해 봅시다.

  * 한 사람이 할 말을 생각하고 (메시지 생성)
  * 입이나 손으로 생각한 말을 합니다.(메시지 송신)
  * 그러면 다른 한 사람이 귀나 눈으로 그 말을 이해하고 (메시지 수신)
  * 말의 의미를 이해하고 다음 말을 합니다. (행동, 결과)   
"메시지 생성 → 메시지 송신 → 메시지 수신 → 행동/결과"   
전통적인 방식에 따르면 서버는 요청서(메시지)를 적어서 클라이언트에게 전송합니다. 클라이언트가 이를 수신하면 해당 메시지를 수신하고 읽고 행동을 수행하거나 판단합니다. 위 두 사람의 대화 상황처럼 메시지 생성 → 메시지 송신 → 메시지 수신 → 메시지에 따른 행동으로 진행됩니다.



[{"메시지 전송 단계":"메시지 생성","참고 이미지":"[png](163722331482011b84a68a4e346029197233b69705f53)"},{"메시지 전송 단계":"메시지 송신","참고 이미지":"[png](16372234583643c86a77df937414c89557893c88dfe7c)"},{"메시지 전송 단계":"메시지 수신","참고 이미지":"[png](1637223520910a4bda2879151452db4f8831d0f21c783)"},{"메시지 전송 단계":"메시지에 따른 행동","참고 이미지":"[png](1637223532474323d466a087a4bb6b9527d3de064b96a)"}]

이 방법으로 많은 메시지를 처리하는 것은 효율적이지 않습니다. 메시지 양이 많아질수록 네트워크 비용이 증가하기 때문입니다.   
메시지 전체를 전송하는 대신 동일한 의미를 부호로 전송하면 효율이 높아집니다. 부호를 사용하기 때문에 송수신 전후에 부호화, 복호화 과정이 필요하지만, 메시지 전체를 보내는 것보다 적은 용량으로 통신할 수 있습니다. 예를 들어 Code-45라는 메시지는 X 초간 춤을 춘다는 명령이라고 서로 약속하고 있으면 Code-45와 3초라는 아주 적은 용량의 정보만으로도 통신할 수 있습니다.

[{"메시지 전송 단계":"메시지 생성","참고 이미지":"[png](163722331482011b84a68a4e346029197233b69705f53)"},{"메시지 전송 단계":"메시지 부호화","참고 이미지":"[png](1637223614276ad339b770dcc450abfd2f27fed8919dd)"},{"메시지 전송 단계":"메시지 송신","참고 이미지":"[png](1637223644960d8351081fc44495b9425a32213d9b15d)"},{"메시지 전송 단계":"메시지 수신","참고 이미지":"[png](1637223663644d87a77c2927c414a91198c3ebf796f08)"},{"메시지 전송 단계":"메시지 복호화","참고 이미지":"[png](1637223677232e564c75135114f74921e9fbd0cdcb2af)"},{"메시지 전송 단계":"메시지에 따른 행동","참고 이미지":"[png](1637223532474323d466a087a4bb6b9527d3de064b96a)"}]

정보의 양은 줄었지만, 단계가 늘어나면서 조금은 복잡해졌습니다. 메이플스토리 월드에서는 위 과정을 간편하게 제어할 수 있는 실행 제어를 제공합니다. 실행 제어는 서버 - 클라이언트 간 통신을 도와주며 복잡한 통신 흐름을 쉽게 제어할 수 있게 해줍니다. 능숙하게 사용하려면 시간이 필요하지만, 클라이언트 - 서버 통신을 활용하려면 실행 제어를 반드시 이해해야 합니다.

# 실행 제어

실행 제어의 동작 방식을 이해하기 위해 함수 호출 상황을 살펴봅시다. 특정한 소리 효과를 내는 `PlaySoundEffect` 함수가 있다고 가정합니다. 소리 효과는 개인 PC에서 나야 하므로 클라이언트에서만 사용하는 함수일 것입니다. 만약, 서버에서 특정 타이밍에 소리 효과를 내야 한다면, 서버는 효과음과 연관된 메시지를 생성해 송신하고, 클라이언트는 수신부를 구현해 `PlaySoundEffect` 함수를 호출합니다.

[{"메시지 전송 흐름":"메시지 생성","참고 이미지":"[png](1637225064752e7bc9e15f693429abef9dbc62bf3b593)"},{"메시지 전송 흐름":"메시지 부호화","참고 이미지":"[png](1637225077456c4d173de75e5465999bd4414dffee512)"},{"메시지 전송 흐름":"메시지 송신","참고 이미지":"[png](1637225094693f9b508dae1af4fa6b39c3412894d527d)"},{"메시지 전송 흐름":"메시지 수신","참고 이미지":"[png](163722510582956aa6c66e3f14d40b7d1a87b3c97c7fd)"},{"메시지 전송 흐름":"메시지 복호화","참고 이미지":"[png](16372251213042e8a4e6a2e8f4944840bfa40e7d7f4d7)"},{"메시지 전송 흐름":"메시지에 함수 호출","참고 이미지":"[png](1637294677515eca9f93011ce412bb9aac3b0d8764bc5)"},{"메시지 전송 흐름":"실행","참고 이미지":"[png](163722545859347bd24cd99514181ae555fba18421eae)"}]

이 과정은 최초 방식보다는 낫지만, 반복해야 한다면 불필요한 과정도 반복되므로 효율이 떨어집니다. 단순하게 서버에서 클라이언트의 PlaySoundEffect를 직접 부르는 상황을 생각해 봅시다. [png](1637294746599f3113cdff1cd432f9015952acfc95055)

만일 위와 같이 서버에서 직접 클라이언트의 함수를 직접 호출한다 해도 불편함은 남아있습니다. 메이플스토리 월드의 컴포넌트는 서버, 클라이언트 공용으로 사용합니다. 그러므로 컴포넌트에 속한 함수를 서버 또는 클라이언트에서 호출할 수 있습니다. 호출한 함수가 어떤 공간(서버 혹은 클라이언트)에서 호출되었는지, 어떤 공간에서 실행되어야 하는지 알고 있어야 합니다.   
예를 들어 "만일 현재 공간이 클라이언트라면 PlaySoundEffect를 실행하고, 서버라면 클라이언트의 PlaySoundEffect를 실행하라."라는 코드를 작성하려면 분기문을 이용해야 합니다.
    
    
    @Component
    script SoundEffectCaller extends Component
        method void CallPlaySoundEffect()
        if isClient == true then
        --PlaySoundEffect를 실행하라!
    
        elseif isServer == true then
        --클라이언트의 PlaySoundEffect를 실행하라!
        end
        end
    end
    

  
다른 공간에 있는 함수를 호출할 수는 있지만, 함수를 호출할 때마다 매번 분기를 태워야 하므로 이 또한 효율적이지 않습니다. 메이플스토리 월드에서는 함수 별로 호출 및 실행 공간을 설정할 수 있습니다. 예를 들어, 클라이언트에서 실행되는 `PlaySoundEffect` 함수를 서버에서 호출하도록 설정하면, `PlaySoundEffect`를 호출하는 함수에서 별도의 분기문을 태우지 않아도 알아서 클라이언트에서 실행됩니다. ```lua @Component script SoundEffectPlayer extends Component method void CallPlaySoundEffect() self:PlaySoundEffect() -- 어떤 공간에서든 같은 공간의 함수를 호출하듯이 사용할 수 있다. end 
    
    
    method void PlaySoundEffect()
    --코드 작성 공간입니다.
    end
    

end
    
    
    # 실행 제어의 종류
    
    함수별로 6가지의 실행 제어를 설정할 수 있고, 옵션에서 설정을 변경할 수 있습니다. 기본적으로 실행 제어를 사용하지 않으면 공용 함수가 되며, 호출하는 공간을 기준으로 동작합니다.
    
    | 실행 제어 설정 | 서버에서 실행 시 | 클라이언트에서 실행 시 |
    | -------- | ------------- | ------------- |
    | 사용하지 않음 | 서버에서 실행됨 | 클라이언트에서 실행됨 |
    | client | 클라이언트로 전송하여 클라이언트에서 실행됨 | 클라이언트에서 실행됨 |
    | client only | 무시됨 | 클라이언트에서 실행됨 |
    | server | 서버에서 실행됨 | 서버로 전송하여 서버에서 실행됨 |
    | sever only | 서버에서 실행됨 | 무시됨 |
    | multicast | 서버에서 실행된 이후 클라이언트로 전송하여 클라이언트에서 실행됨 | 무시됨 |
    
    [png](1637302950063ab2e4b57ae124ea59a38a38028a1c148)
    # 실행 제어 설정 방법
    
    함수를 추가하고 [png](16345206995612a35d54577d8466da03a9fe452a5218c) **[메뉴]** 버튼을 누른 뒤, **Excution Space Setting**을 클릭해 실행 공간을 설정합니다. 실행 제어를 사용하지 않으려면 **사용하지 않음**을 선택합니다.
    
    [png](168178285797811b83332e2ca4e9ab49ca4268be34b27)
    # 실행 제어 표시
    
    스크립트 함수의 실행 제어 설정을 아이콘으로 표시합니다.
    아이콘은 다음과 같습니다.
    
    | Icon | 실행 제어 설정 |
    | ---- | -------- |
    | 없음 | 사용하지 않음 |
    | [png](1634520790600fcf43ce1e20540d99206368bae5bfb29) | Client |
    | [png](16345204384345f2159bf5f7a4aadae190a6e66646a77) | ClientOnly |
    | [png](1634520851421b73268f466814035ba41893c8b45fd9c) | Server |
    | [png](1634520890852542e95e200a647819d47b85d5c4d157e) | ServerOnly |
    | [png](1634520769851d04d006c9114421f8e9ce34d5c47b721) | Multicast |
    
    실행 제어가 설정된 함수에는 실행 제어 아이콘이 표시됩니다. 
    실행될 수 없는 함수는 [png](1635387179103da42aef656b24301ba63221cd02c4bbf) 아이콘이 표시됩니다.
    [png](16817831861368eaf4d7f03b545759cb8f9a98c291601)
    # 실행 제어 함수의 매개 변수
    
    서로 다른 공간의 함수를 실행할 때 매개 변수를 통해 다른 공간으로 데이터를 전송할 수 있습니다. 
    예를 들어 서버에서 클라이언트에 표시되는 시스템 메시지를 출력하려면 시스템 메시지를 출력하는 클라이언트 함수를 서버에서 호출하고 함수의 매개 변수로 메시지를 전달합니다.
    

@Component script OutOfBoundsDetector extends Component @ExecSpace("ServerOnly") method void OnUpdate(number delta) local users = _UserService.UserEntities local message = "Out of bounds" \-- 모든 플레이어를 체크하며 플레이어가 특정 좌표 밖으로 나갔을 때 메시지 출력 for id, user in pairs(users) do local posX = user.TransformComponent.Position.x if posX > 100 then \-- 메시지를 클라이언트 함수의 매개 변수로 전달 self:PrintSystemMessage(message) end end end
    
    
    @ExecSpace("ClientOnly")
    method void PrintSystemMessage(string message)
    log(message)
    end
    

end
    
    
      
    
    이처럼 실행 제어 함수의 매개 변수를 통해 다른 공간으로 데이터를 전송할 수 있습니다. 하지만 모든 데이터를 전송할 수 있는 것은 아닙니다. 실행 제어 함수에서 사용할 수 있는 매개 변수 타입을 살펴봅시다.
    | 타입 | 사용 가능 여부 |
    | --- | --- |
    | any | 사용 불가 |
    | string | 사용 가능 |
    | integer | 사용 가능 |
    | number | 사용 가능 |
    | boolean | 사용 가능 |
    | table | 사용 가능 |
    | SyncTable< v > | 제한적 사용 가능 |
    | SyncTable< k, v > | 제한적 사용 가능 |
    | Vector2 | 사용 가능 |
    | Vector3 | 사용 가능 |
    | Vector4 | 사용 가능 |
    | Color | 사용 가능 |
    | Entity | 사용 가능 |
    | Component | 사용 가능 |
    | EntityRef | 사용 가능 |
    | ComponentRef | 사용 가능 |
      
    
    **any** 타입을 제외한 모든 타입을 매개 변수로 사용할 수 있습니다. 하지만 SyncTable< v > 나 SyncTable< k , v >의  k 또는 v 타입은 목록에 나오는 것만 사용할 수 있습니다.
    [png](1685938861944b5fa20659b7d4b3c809a765906fa012c)
    
    ## 실행 공간이 Client인 함수의 매개 변수 특수성
    Client A에서 서버 함수를 실행하고 서버에서는 그에 대한 응답을 보내는 상황을 생각해봅시다. 보통 이런 경우에 서버는 모든 클라이언트에 응답을 보냅니다.
    [png](168075980289219c2a09110834a149783309cd2192038)
    
    하지만 어떤 경우에는 처음 요청을 보낸 Client A에게만 응답하면 되는 상황도 있습니다.
    [png](168075982031597f4c778957346d1969d09fdd4ff3eb0)
    
    메이플스토리 월드에서는 특정 클라이언트에만 응답을 보내는 기능을 제공합니다. 이 기능을 사용하는 방법은 "실행 공간이 Client인 함수의 마지막 매개 변수에 **보내려는 UserId**를 적는 것"입니다. 이 기능의 사용 예시를 보다 자세히 살펴보려면 [Effective MSW 1](/docs/?postId=559) 가이드에서 "특정 클라이언트에만 응답 보내기" 내용을 참고해 봅시다.