FastVector3을 활용한 월드 최적화

# 학습 과정 소개

`FastVector3`을 사용하는 방법과 월드 최적화에 활용하는 방법에 대해 알아봅시다.

# Fast 타입

`FastVector2`, `FastVector3`, `FastColor` 타입은 Lua Table로 작성되어 기존의 `Vector2`, `Vector3`, `Color`를 보완할 수 있습니다 .두 종류의 타입 모두 같은 기능을 제공하지만, Fast 접두사가 붙은 타입은 월드에 부하가 발생할 때 최적화 용도로 사용할 수 있습니다.

# FastVector3 특징

FastVector3의 5가지 특징은 다음과 같습니다.

  1. `FastVector3`은 Lua Table로 구현되어 `Vector3`에 비해 가볍고 유연합니다. [png](174401387084131f34bede9bb4db18195d9de68fcd798)
  2. `FastVector3`은 Vector3의 모든 프로퍼티와 함수가 구현되어 있으며, Vector3으로 자동 변환을 지원합니다.
  3. `FastVector3`은 동기화를 지원하지 않습니다. 그러므로 다른 공간 실행 함수의 매개변수로 사용할 수 없습니다.
  4. `FastVector3`은 8 Byte 실수형 값을 사용하고, `Vector3`은 4 Byte 실수형을 사용합니다. 이로 인해 두 타입 사이의 유효 범위와 연산 결과는 차이가 발생할 수 있습니다.
  5. `FastVector3`은 zero, forward 등의 static 프로퍼티에 접근할 때 원본의 참조를 반환하는 반면, Vector3은 복제된 값을 반환합니다.



# FastVector3 사용

#### 생성자 변경

`Vector3`의 생성자를 `FastVector3`의 생성자로 변경해 사용할 수 있습니다. 연산이 많아질 수록 `FastVector3`과 `Vector3`의 성능 차이가 커집니다.

Vector3은 아래와 같은 방식으로 사용합니다.
    
    
    @Component
    script PositionInitializer extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
            -- Vector3을 사용할 때
            local newPosition = Vector3(0, 0, 0)
            newPosition.x = 1
            newPosition.y = 2
            self.Entity.TransformComponent.Position =  newPosition
        end
    end
    

FastVector3은 아래와 같이 사용합니다.
    
    
    @Component
    script FixedPositionSetter extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
            local newPositionFast = FastVector3(0, 0, 0)
            newPositionFast.x = 1
            newPositionFast.y = 2
            self.Entity.TransformComponent.Position = newPositionFast
        end
    end
    

#### Static 프로퍼티 사용

`FastVector3`의 Static 프로퍼티를 사용할 때는 주의가 필요합니다. `FastVector3`의 특징 중 하나인 Static 프로퍼티 접근 시 원본의 참조를 변환하는 특성으로 인해 Static 프로퍼티에 직접 접근하면 해당 Static 프로퍼티의 값이 모두 변경될 수 있습니다. 아래와 같이 `Clone()` 함수를 사용해 값을 복제해 사용해야 합니다.
    
    
    @Component
    script VectorXInitializer extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
            local vt = FastVector3.zero:Clone() 
            vt.x = 1
        end
    end
    

`FastVector3`은 `FastVector3.zero`의 프로퍼티 값을 변경하면 `FastVector3.zero`가 변경됩니다.
    
    
    @Component
    script VectorXConfiguration extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
            local vt = FastVector3.zero
            vt.x = 1 -- FastVector3.zero : FastVector(1,0,0)
        end
    end
    

`Vector3`은 아래와 같이 `Vector3.zero`의 프로퍼티 값을 변경하여도 `Vector3.zero`가 변경되지 않습니다.
    
    
    @Component
    script VectorInitializer extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
            local vt = Vector3.zero
            vt.x = 1 -- Vector3.zero : Vector(0,0,0)
        end
    end
    

#### 매개변수 타입 지정

`FastVector3`은 프로퍼티 타입, 함수의 매개변수로 지정할 수 없습니다. 그러므로 `FastVector3`을 타입이나 매개 변수로 사용할 때는 any, table 타입을 사용해야 합니다. 특히 mLua를 사용할 때는 any 타입을 사용하고, 어노테이션(Annotation)을 함께 사용하기를 권장합니다.
    
    
    @Component
    script EmptyScript_1300_2440 extends Component
    end
    

#### FastVector3 기능 확장

`FastVector3`은 함수나 프로퍼티를 추가하여 기능을 확장할 수 있습니다. 또한 기존 함수나 프로퍼티의 값을 변경하는 것도 가능합니다.
    
    
    @Component
    script FastVector3Setup extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
            -- static 프로퍼티 추가
            FastVector3.two = FastVector3(2, 2, 2)
            
            -- 함수 추가
            local v3mt = getmetatable(FastVector3.zero)
            v3mt.NewFunction = function(self)
                -- ...
            end
        end
    end
    

# FastVector3 효율적으로 사용하기

`FastVector3` 타입을 사용해 참조 객체 생성을 줄일 수 있습니다. 메이플스토리 월드에서는 Lua에서 Native 타입에 접근 시 참조 객체가 생성됩니다. 이는 참조되는 객체가 파괴되지 않게 하기 위함입니다. 이렇게 생성된 참조 객체는 사용이 끝나면, Lua의 Lua Garbage Collector에서 먼저 메모리가 정리되고, 그 후 Native의 Garbage Collector에서 다시 한 번 정리 작업을 거쳐 메모리에서 완전히 제거되게 됩니다.

#### 참조 객체 생성하지 않게 사용하기

최적화를 위해 `Vector3` 대신 `FastVector3`을 사용하는 이유는 값을 생성하거나, 읽어올 때 참조 객체를 생성하지 않기 때문입니다. `Vector3`의 경우 Lua 스크립트에서 사용하기 위해 참조 객체가 필요합니다. 메이플스토리 월드의 `Vector3` 객체를 사용하면 참조 객체가 자동으로 생성, 파괴됩니다. 그러나 이 과정에서 처리 시간이 길어지고 메모리 사용량이 증가해 최적화가 필요하게 됩니다.
    
    
    @Component
    script TransformVectorInitializer extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
            -- Position 값을 읽어올 때 참조 객체가 생성됨
            local position = self.Entity.TransformComponent.Position
            
            -- Vector3 객체를 생성할 때 참조 객체가 생성됨
            local vt = Vector3(0, 0, 0)
        end
    end
    
    
    
    @Component
    script PositionInitializer extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
            -- 참조 객체가 생성됨
            local newPosition = Vector3(0, 0, 0)
            -- 참조 객체가 생성되지 않음
            newPosition.x = 1
            newPosition.y = 2
            self.Entity.TransformComponent.Position =  newPosition
        end
    end
    

참조 객체를 생성하지 않는 `FastVector3` 사용 예제는 아래와 같습니다.
    
    
    @Component
    script FixedPositionSetter extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
            -- 참조 객체가 생성되지 않음
            local newPositionFast = FastVector3(0, 0, 0)
            
            -- 참조 객체가 생성되지 않음
            newPositionFast.x = 1
            newPositionFast.y = 2
            -- 네이티브 프로퍼티에 FastVector3을 대입할 때는 참조 객체가 생성되지 않음
            self.Entity.TransformComponent.Position =  newPositionFast
        end
    end
    

`TransformComponent`의 `Position`과 `WorldPosition` 프로퍼티 타입은 `Vector3` 입니다. 그러므로 값을 읽어올 때 참조 객체가 생성됩니다. 두 프로퍼티를 참조 객체를 생성하지 않고 사용하고 싶다면, `PositionAsFastVector3()`, `WorldPositionAsFastVector3()` 함수를 활용할 수 있습니다.

> Tip. `UITransformComponent`도 `PositionAsFastVector3()`, `WorldPositionAsFastVector3()` 함수를 사용해 동일한 방법으로 활용할 수 있습니다.

`TransformComponent`의 Position 값을 변경하는 코드의 성능을 비교해봅시다.
    
    
    @Component
    script PositionBenchmark extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
            local transform = self.Entity.TransformComponent
            
            -- 평균 1.113 us
            local position1 =  transform:PositionAsFastVector3() -- 참조 객체가 생성되지 않음
            
            position1.x = position1.x + 0.1
            position1.y = position1.y + 0.1
            transform.Position = position1
            
            -- 평균 2.217 us
            local position2 =  transform.Position --  참조 객체가 생성됨
            
            position2.x = position2.x + 0.1
            position2.y = position2.y + 0.1
            
            -- 평균 1.876 us
            local position3 =  FastVector3(transform.Position) -- 참조 객체가 생성됨
            
            position3.x = position3.x + 0.1
            position3.y = position3.y + 0.1
            transform.Position = position3
        end
    end
    

`Vector3` 스크립트 프로퍼티에 `FastVector3`을 대입하면 `Vector3`로 자동 변환됩니다. 이 자동 변환으로 인해 크리에이터의 의도대로 `FastVector3`이 동작하지 않을 수 있으므로 아래와 같은 사용은 지양해야 합니다. 타입을 Vector3이 아닌 any 또는 table로 지정해야 자동 변환으로 인한 문제가 발생하지 않습니다.
    
    
    @Component
    script VectorPropertyHandler extends Component
        property Vector3 vt3 = Vector3(0, 0, 0)
    
        method void Test()
        -- FastVector3가 Vector3으로 자동 변환
        self.vt3 = FastVector3(1, 1, 1)
    
        -- 로컬 변수가 Vector3를 참조하면서 참조 객체가 생성
        local vt = self.vt3
        end
    end
    

# FastVector3와 인덱스 활용

`FastVector3`은 인덱스를 이용해 접근할 수도 있습니다. 아래 표를 보면 인덱스를 이용한 접근이 키를 이용한 접근보다 약간 더 빠른 것을 알 수 있습니다.

[png](17453667314987383595f55d84737a7cb12f62068050b)

인덱스를 이용해 `FastVector3`에 접근하는 예제는 아래와 같습니다. x는 1, y는 2, z는 3에 대응됩니다.
    
    
    @Component
    script FastVector3Demo extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
            local v = FastVector3(1, 2, 3)
            print(v.x, v.y, v.z)      -- Output: 1 2 3
            print (v[1], v[2], v[3])  -- Output: 1 2 3
        end
    end
    

인덱스 접근은 월드 성능 최적화에 도움이 됩니다. 하지만 x, y, z 멤버를 직접 접근하는 것보다 상대적으로 가독성이 낮습니다. 그러므로 인덱스를 이용한 접근은 월드에 최대한의 최적화가 필요한 상황에서만 사용하기를 권유합니다. 예를 들어 `Normalize()` 함수 호출 빈도가 지나치게 높은 경우, 아래와 같이 인덱스 접근을 사용하면 성능을 개선할 수 있습니다.
    
    
    @Component
    script VectorNormalizeOverride extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
            local v3mt = getmetatable(FastVector3.zero)
            
            -- FastVector3의 Normalize 함수를 수정
            v3mt.Normalize = function(self)
                local x, y, z = self[1], self[2], self[3] -- self.x , self.y , self.z
                local length = (x*x + y*y + z*z) ^ 0.5
            
                if length == 0 then
                    return FastVector3(x, y, z)
                end
            
                return FastVector3(x / length, y / length, z / length)
            end
        end
    end