

지원 종료 공지: 2026 AWS 년 5월 20일에에 대한 지원이 종료됩니다 AWS SimSpace Weaver. 2026년 5월 20일 이후에는 SimSpace Weaver 콘솔 또는 SimSpace Weaver 리소스에 더 이상 액세스할 수 없습니다. 자세한 내용은 [AWS SimSpace Weaver 지원 종료를 참조하세요](https://docs.aws.amazon.com/simspaceweaver/latest/userguide/simspaceweaver-end-of-support.html).

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

# AWS SimSpace Weaver 앱 SDK
<a name="working-with_app-sdk"></a>

 SimSpace Weaver 앱 SDK는 시뮬레이션에서 개체를 제어하고 SimSpace Weaver 이벤트에 응답하는 데 사용할 수 있는 APIs를 제공합니다. 여기에는 다음 네임스페이스가 포함됩니다.
+ **API** - API의 핵심 정의 및 해당 사용

다음 라이브러리와 연결합니다.
+ `libweaver_app_sdk_cxx_v1_full.so`

**중요**  
라이브러리는 AWS 클라우드에서 앱을 실행할 때 동적 연결에 사용할 수 있습니다. 앱과 함께 업로드할 필요가 없습니다.

**참고**  
 SimSpace Weaver 앱 SDK APIs는 시뮬레이션 내에서 데이터를 제어합니다. 이러한 APIs는 SimSpace Weaver 서비스 리소스(예: 시뮬레이션, 앱 및 클럭)를 제어하는 SimSpace Weaver 서비스 APIs와는 별개입니다 AWS. 자세한 내용은 [SimSpace Weaver API 참조](api-reference.md) 단원을 참조하십시오.

**Topics**
+ [API 메서드는 Result를 반환합니다.](working-with_app-sdk_return-result.md)
+ [최상위의 앱 SDK와 상호 작용](working-with_app-sdk_top-level.md)
+ [시뮬레이션 관리](working-with_app-sdk_sim.md)
+ [구독](working-with_app-sdk_sub.md)
+ [개체](working-with_app-sdk_ent.md)
+ [엔터티 이벤트](working-with_app-sdk_events.md)
+ [Result 및 오류 처리](working-with_app-sdk_result.md)
+ [일반 및 도메인 유형](working-with_app-sdk_generics.md)
+ [기타 앱 SDK 작업](working-with_app-sdk_misc.md)

# API 메서드는 Result를 반환합니다.
<a name="working-with_app-sdk_return-result"></a>

대부분의 SimSpace Weaver API 함수에는 반환 유형이 있습니다`Aws::WeaverRuntime::Result<T>`. 함수가 성공적으로 실행되면 `Result`에 `T`가 포함됩니다. 그렇지 않으면 `Result`에 Rust App SDK의 오류 코드를 나타내는 `Aws::WeaverRuntime::ErrorCode`가 포함됩니다.

**Example 예제**  

```
Result<Transaction> BeginUpdate(Application& app)
```

이 메서드는 다음을 수행합니다.
+ `BeginUpdate()` 실행이 성공하면 `Transaction`을 반환합니다.
+ `BeginUpdate()` 실행이 실패하면 `Aws::WeaverRuntime::ErrorCode`를 반환합니다.

# 최상위의 앱 SDK와 상호 작용
<a name="working-with_app-sdk_top-level"></a>

**수명 주기**
+  SimSpace Weaver 앱 SDK는 앱 수명 주기를 관리합니다. 앱의 수명 주기 상태를 읽거나 쓸 필요가 없습니다.

**파티션**
+ `Result <PartitionSet> AssignedPartitions(Transaction& txn);`를 사용하여 소유한 파티션을 가져옵니다.
+ `Result <PartitionSet> AllPartitions(Transaction& txn);`를 사용하여 시뮬레이션의 모든 파티션을 가져옵니다.

# 시뮬레이션 관리
<a name="working-with_app-sdk_sim"></a>

이 섹션에서는 일반적인 시뮬레이션 관리 작업에 대한 솔루션을 설명합니다.

**Topics**
+ [시뮬레이션 시작](working-with_app-sdk_sim_start.md)
+ [시뮬레이션 업데이트](working-with_app-sdk_sim_update.md)
+ [시뮬레이션 종료](working-with_app-sdk_sim_terminate.md)

# 시뮬레이션 시작
<a name="working-with_app-sdk_sim_start"></a>

`CreateApplication()`을 사용하여 앱을 생성합니다.

**Example 예제**  

```
Result<Application> applicationResult = Api::CreateApplication();

if (!applicationResult)
{
    ErrorCode errorCode = WEAVERRUNTIME_EXPECT_ERROR(applicationResult);

    std::cout << "Failed to create application. Error code " <<
        static_cast<std::underlying_type_t<ErrorCode>>(errorCode) <<
        " Last error message "<< Api::LastErrorMessage() << ".";

    return 1;
}

/**
* Run simulation
*/
RunSimulation(std::move(applicationResult.assume_value()));
```

# 시뮬레이션 업데이트
<a name="working-with_app-sdk_sim_update"></a>

다음 `BeginUpdate` 함수를 사용하여 앱을 업데이트합니다.
+ `Result<Transaction> BeginUpdate(Application& app)`
+ `Result<bool> BeginUpdateWillBlock(Application& app)` - `BeginUpdate()`의 차단 여부를 알려줍니다.

`Result<void> Commit(Transaction& txn)`을 사용하여 변경 내용을 커밋합니다.

**Example 예제**  

```
Result<void> AppDriver::RunSimulation(Api::Application app) noexcept
{
    while (true)
    {
        {
            bool willBlock;

            do
            {
                WEAVERRUNTIME_TRY(willBlock, Api::BeginUpdateWillBlock(m_app));
            } while (willBlock);
        }

        WEAVERRUNTIME_TRY(Transaction transaction, Api::BeginUpdate(app));

        /**
         * Simulate app.
         */
        WEAVERRUNTIME_TRY(Simulate(transaction));
        WEAVERRUNTIME_TRY(Api::Commit(std::move(transaction)));
    }

    return Success();
}
```

# 시뮬레이션 종료
<a name="working-with_app-sdk_sim_terminate"></a>

`Result<void> DestroyApplication(Application&& app)`을 사용하여 앱과 시뮬레이션을 종료합니다.

다른 앱은 `BeginUpdateWillBlock()` 또는 `BeginUpdate()`에 대한 호출에서 `ErrorCode::ShuttingDown`이 수신되는 경우 시뮬레이션이 종료된다는 사실을 알게 됩니다. 앱이 `ErrorCode::ShuttingDown`을 수신하면 `Result<void> DestroyApplication(Application&& app)` 호출을 자체적으로 종료할 수 있습니다.

**Example 예제**  

```
Result<void> AppDriver::EncounteredAppError(Application&& application) noexcept
{
    const ErrorCode errorCode = WEAVERRUNTIME_EXPECT_ERROR(runAppResult);

    switch (errorCode)
    {
    case ErrorCode::ShuttingDown:
        {
            // insert custom shutdown process here.

            WEAVERRUNTIME_TRY(Api::DestroyApplication(std::move(application)));
            return Success();
        }
    default:
        {
            OnAppError(errorCode);
            return errorCode;
        }
    }
}
```

**중요**  
`Api::Commit()` 다음에 `Result<void> DestroyApplication(Application&& app)`을 호출합니다. 업데이트 중에 애플리케이션을 삭제하면 정의되지 않은 동작이 발생할 수 있습니다.

**중요**  
프로그램이 종료되기 전에 `DestroyApplication()`을 호출하여 애플리케이션이 성공적으로 종료되었다고 보고하는지 확인해야 합니다.  
프로그램이 종료될 때 `DestroyApplication()`을 호출하지 않으면 `FATAL` 상태로 간주됩니다.

# 구독
<a name="working-with_app-sdk_sub"></a>

구독 영역과 도메인 ID를 사용하여 구독을 생성합니다. 도메인 ID는 해당 구독 영역을 소유한 도메인을 나타냅니다. `BoundingBox2F32`는 구독 영역을 설명합니다. 다음 함수를 사용하여 구독을 생성합니다.

```
Result<SubscriptionHandle> CreateSubscriptionBoundingBox2F32(Transaction& txn, DomainId id, const BoundingBox2F32& boundingBox)
```

**Example 예제**  

```
Result<void> CreateSubscriptionInSpatialDomain(Transaction& transaction)
{
    WEAVERRUNTIME_TRY(Api::PartitionSet partitionSet, Api::AllPartitions(transaction)); 
    
    Api::DomainId spatialDomainId;

    for (const Api::Partition& partition : partitionSet.partitions)
    {
        if (partition.domain_type == Api::DomainType::Spatial)
        {
            /**
            * Get the spatial domain ID.
            */
            spatialDomainId = partition.domain_id;
            break;
        }
    }
    
    constexpr Api::BoundingBox2F32 subscriptionBounds { 
        /* min */ { /* x */ 0, /* y */ 0 }, 
        /* max */ { /* x */ 1000, /* y */ 1000 } }

    WEAVERRUNTIME_TRY(
        Api::SubscriptionHandle subscriptionHandle,
        Api::CreateSubscriptionBoundingBox2F32(
        transaction,
        spatialDomainId,
        subscriptionBounds));
        
    return Success();
}
```

`CreateSubscriptionBoundingBox2F32()`에서 반환된 `Api::SubscriptionHandle`을 사용하여 구독을 수정할 수 있습니다. 다음 함수에 인수로 전달합니다.

```
Result<void> ModifySubscriptionBoundingBox2F32(Transaction& txn, SubscriptionHandle handle, const BoundingBox2F32& boundingBox)
```

```
Result<void> DeleteSubscription(Transaction& txn, SubscriptionHandle handle)
```

# 개체
<a name="working-with_app-sdk_ent"></a>

`CreateEntity()`에서 반환된 `Result<Api::Entity>`의 `Api:Entity`을 사용하거나 엔터티가 앱의 구독 영역에 진입할 때 소유권 변경 이벤트에서 `Store` 및 `Load` API를 호출합니다(자세한 내용은 [엔터티 이벤트](working-with_app-sdk_events.md) 섹션 참조). 이러한 API와 함께 사용할 수 있도록 `Api::Entity` 객체를 추적하는 것이 좋습니다.

**Topics**
+ [엔터티 생성](working-with_app-sdk_ent_create.md)
+ [공간 도메인으로 엔터티 이전](working-with_app-sdk_ent_transfer.md)
+ [엔터티에 필드 데이터 쓰기 및 읽기](working-with_app-sdk_ent_readwrite.md)
+ [엔터티에 대한 위치 저장](working-with_app-sdk_ent_store-position.md)
+ [엔터티에 대한 위치 로드](working-with_app-sdk_ent_load-position.md)

# 엔터티 생성
<a name="working-with_app-sdk_ent_create"></a>

`CreateEntity()`를 사용하여 엔터티를 생성합니다. 이 함수에 전달하는 `Api::TypeId`의 의미를 정의합니다.

```
Namespace
{
    constexpr Api::TypeId k_entityTypeId { /* value */ 512 };
}

Result<void> CreateEntity(Transaction& transaction)
{
    WEAVERRUNTIME_TRY(
        Api::Entity entity,
        Api::CreateEntity(
            transaction, Api::BuiltinTypeIdToTypeId(k_entityTypeId )));
}
```

**참고**  
`Api::BuiltinTypeId`에 0\$1511의 값이 예약되어 있습니다. 엔터티 TypeID(이 예제에서는 `k_entityTypeId`)의 값은 512 이상이어야 합니다.

# 공간 도메인으로 엔터티 이전
<a name="working-with_app-sdk_ent_transfer"></a>

사용자 지정 앱 또는 서비스 앱이 엔터티를 생성한 후 엔터티가 시뮬레이션에서 공간적으로 존재하도록 하려면 앱이 엔터티를 공간 도메인으로 전송해야 합니다. 공간 도메인의 엔터티는 다른 앱에서 읽을 수 있고 공간 앱에서 업데이트할 수 있습니다. `ModifyEntityDomain()` API를 사용하여 엔터티를 공간 도메인으로 전송할 수 있습니다.

```
AWS_WEAVERRUNTIME_API Result<void> ModifyEntityDomain(Transaction& txn, const Entity& entity, DomainId domainId) noexcept;
```

`DomainId`가 호출 앱에 할당된 `Partition`과 일치하지 않는 경우 `DomainId`는 `DomainType::Spatial` `Domain`에 대한 ID이어야 합니다. 새 `Domain`으로의 소유권 전송은 `Commit(Transaction&&)` 중에 이루어집니다.파라미터

`txn`  
현재의 `Transaction`입니다.

`entity`  
`Domain`를 변경할 대상 `Entity`입니다.

`domainId`  
`Entity`에 대한 대상 `Domain`의 `DomainId`입니다.

이 API는 엔터티 도메인이 성공적으로 변경되면 `Success`를 반환합니다.

# 엔터티에 필드 데이터 쓰기 및 읽기
<a name="working-with_app-sdk_ent_readwrite"></a>

모든 엔터티 데이터 필드는 blob 유형입니다. 엔터티에 최대 1,024바이트의 데이터를 쓸 수 있습니다. Blob 크기가 클수록 성능이 저하되므로 blob을 최대한 작게 유지하는 것이 좋습니다. Blob에 쓸 때 포인터 SimSpace Weaver 를 데이터 및 길이에 전달합니다. Blob에서 읽을 때 SimSpace Weaver 는 포인터 및 읽을 길이를 제공합니다. 앱이 `Commit()`을 호출하기 전에 모든 읽기가 완료되어야 합니다. 읽기 호출에서 반환된 포인터는 앱이 `Commit()`을 호출할 때 무효화됩니다.

**중요**  
`Commit()` 이후에 캐시된 blob 포인터에서의 읽기는 지원되지 않으므로 시뮬레이션이 실패할 수 있습니다.
읽기 호출에서 반환된 blob 포인터에서의 쓰기는 지원되지 않으므로 시뮬레이션이 실패할 수 있습니다.

**Topics**
+ [엔터티의 필드 데이터 저장](working-with_app-sdk_ent_readwrite_store.md)
+ [엔터티의 필드 데이터 불러오기](working-with_app-sdk_ent_readwrite_load.md)
+ [제거된 엔터티의 필드 데이터 로드](working-with_app-sdk_ent_readwrite_load-removed.md)

# 엔터티의 필드 데이터 저장
<a name="working-with_app-sdk_ent_readwrite_store"></a>

다음 예제는 앱이 소유한 엔터티의 필드 데이터를 저장(상태 패브릭에 쓰기)하는 방법을 보여줍니다. 해당 예제에는 다음 함수가 사용됩니다.

```
AWS_WEAVERRUNTIME_API Result<void> StoreEntityField(
    Transaction& txn,
    const Entity& entity,
    TypeId keyTypeId,
    FieldIndex index,
    std::int8_t* src,
    std::size_t length) noexcept;
```

`Api::TypeId keyTypeId` 파라미터는 전달된 데이터의 데이터 유형을 나타냅니다.

`Api::TypeId keyTypeId` 파라미터는 `Api::BuiltinTypeId`에서 해당 `Api::TypeId`를 수신해야 합니다. 적절한 변환이 없는 경우 `Api::BuiltinTypeId::Dynamic`을 사용할 수 있습니다.

복잡한 데이터 유형의 경우 `Api::BuiltInTypeId::Dynamic`을 사용합니다.

**참고**  
`FieldIndex index` 값은 0보다 커야 합니다. 0 값은 인덱스 키용으로 예약되어 있습니다(`StoreEntityIndexKey()` 참조).

**Example 프리미티브 데이터 유형을 사용한 예제**  

```
namespace
{
    constexpr Api::FieldIndex k_isTrueFieldId { /* value */ 1 };
}

Result<void> SetEntityFields(
    Api::Entity& entity, 
    Transaction& transaction)
{
    bool value = true;
    
    auto* src = reinterpret_cast<std::int8_t*>(value);
    size_t length = sizeof(*value);
    
    WEAVERRUNTIME_TRY(Api::StoreEntityField(
        transaction,
        entity,
        Api::BuiltinTypeIdToTypeId(
            Aws::WeaverRuntime::Api::BuiltinTypeId::Bool),
        k_isTrueFieldId,
        src,
        length));
}
```

**Example struct를 사용한 데이터 보관 예제**  

```
namespace
{
    constexpr Api::FieldIndex k_dataFieldId { /* value */ 1 };
}

struct Data
{
    bool boolData;
    float floatData;
};

Result<void> SetEntityFields(
    Api::Entity& entity, 
    Transaction& transaction)
{
    Data data = { /* boolData */ false, /* floatData */ -25.93 };
    
    auto* src = reinterpret_cast<std::int8_t*>(data);
    size_t length = sizeof(*data);
    
    WEAVERRUNTIME_TRY(Api::StoreEntityField(
        transaction,
        entity,
        Api::BuiltinTypeIdToTypeId(
            Aws::WeaverRuntime::Api::BuiltinTypeId::Dynamic),
        k_dataFieldId,
        src,
        length));
}
```

# 엔터티의 필드 데이터 불러오기
<a name="working-with_app-sdk_ent_readwrite_load"></a>

다음 예제는 엔터티의 필드 데이터를 로드(상태 패브릭에서 읽기)하는 방법을 보여줍니다. 해당 예제에는 다음 함수가 사용됩니다.

```
Result<std::size_t> LoadEntityField(
    Transaction& txn,
    const Entity& entity,
    TypeId keyTypeId,
    FieldIndex index,
    std::int8_t** dest) noexcept;
```

`Api::TypeId keyTypeId` 파라미터는 `Api::BuiltinTypeId`에서 해당 `Api::TypeId`를 수신해야 합니다. 적절한 변환이 없는 경우 `Api::BuiltinTypeId::Dynamic`을 사용할 수 있습니다.

**참고**  
`FieldIndex` 인덱스 값은 0보다 커야 합니다. 0 값은 인덱스 키용으로 예약되어 있습니다(`StoreEntityIndexKey()` 참조).

**Example 프리미티브 데이터 유형을 사용한 예제**  

```
namespace
{
    constexpr Api::FieldIndex k_isTrueFieldId { /* value */ 1 };
}

Result<void> LoadEntityFields(
    Api::Entity& entity, 
    Transaction& transaction)
{
    std::int8_t* dest = nullptr;
    
    WEAVERRUNTIME_TRY(Api::LoadEntityField(
        transaction,
        entity,
        Api::BuiltinTypeIdToTypeId(
            Aws::WeaverRuntime::Api::BuiltinTypeId::Bool),
        k_isTrueFieldId,
        &dest));
    
    bool isTrueValue = *reinterpret_cast<bool*>(dest);
}
```

**Example struct를 사용한 데이터 보관 예제**  

```
namespace
{
    constexpr Api::FieldIndex k_dataFieldId { /* value */ 1 };
}

struct Data
{
    bool boolData;
    float floatData;
};

Result<void> LoadEntityFields(
    Api::Entity& entity, 
    Transaction& transaction)
{
    std::int8_t* dest = nullptr;
    
    WEAVERRUNTIME_TRY(Api::LoadEntityField(
        transaction,
        entity,
        Api::BuiltinTypeIdToTypeId(
            Aws::WeaverRuntime::Api::BuiltinTypeId::Dynamic),
        k_dataFieldId,
        &dest));
    
    Data dataValue = *reinterpret_cast<Data*>(dest);
}
```

# 제거된 엔터티의 필드 데이터 로드
<a name="working-with_app-sdk_ent_readwrite_load-removed"></a>

앱의 소유권 및 구독 영역에서 제거된 엔터티의 엔터티 필드 데이터는 로드(상태 패브릭에서 읽기)할 수 없습니다. 다음 예제에서는 `Api::ChangeListAction::Remove`의 결과로 엔터티에 대한 `Api::LoadIndexKey()` 호출로 인해 오류가 발생합니다. 두 번째 예제는 앱에서 직접 항목 데이터를 저장하고 로드하는 올바른 방법을 보여줍니다.

**Example 잘못된 코드의 예제**  

```
Result<void> ProcessSubscriptionChanges(Transaction& transaction)
{
    /* ... */
    
    WEAVERRUNTIME_TRY(Api::SubscriptionChangeList subscriptionChangeList, 
        Api::AllSubscriptionEvents(transaction));
    
    for (const Api::SubscriptionEvent& event : 
        subscriptionChangeList.changes)
    {
        switch (event.action)
        {
        case Api::ChangeListAction::Remove:
            {
                std::int8_t* dest = nullptr;
    
                /**
                 * Error!
                 * This calls LoadEntityIndexKey on an entity that
                 * has been removed from the subscription area.
                 */
                WEAVERRUNTIME_TRY(Api::LoadEntityIndexKey(
                    transaction,
                    event.entity,
                    Api::BuiltinTypeIdToTypeId(
                        Api::BuiltinTypeId::Vector3F32),
                    &dest));
    
                AZ::Vector3 position = 
                    *reinterpret_cast<AZ::Vector3*>(dest);
                break;
            }
        }
 
    }

    /* ... */
}
```

**Example 앱에서 항목 데이터를 저장하고 로드하는 올바른 방법의 예제**  

```
Result<void> ReadAndSaveSubscribedEntityPositions(Transaction& transaction)
{
    static std::unordered_map<Api::EntityId, AZ::Vector3> 
        positionsBySubscribedEntity;

    WEAVERRUNTIME_TRY(Api::SubscriptionChangeList subscriptionChangeList, 
        Api::AllSubscriptionEvents(transaction));

    for (const Api::SubscriptionEvent& event : 
        subscriptionChangeList.changes)
    {
        switch (event.action)
        {
        case Api::ChangeListAction::Add:
            {
                std::int8_t* dest = nullptr;

                /**
                 * Add the position when the entity is added.
                 */
                WEAVERRUNTIME_TRY(Api::LoadEntityIndexKey(
                    transaction,
                    event.entity,
                    Api::BuiltinTypeIdToTypeId(
                        Api::BuiltinTypeId::Vector3F32),
                    &dest));

                AZ::Vector3 position = 
                    *reinterpret_cast<AZ::Vector3*>(dest);
                positionsBySubscribedEntity.emplace(
                    event.entity.descriptor->id, position);

                break;
            }
        case Api::ChangeListAction::Update:
            {
                std::int8_t* dest = nullptr;

                /**
                 * Update the position when the entity is updated.
                 */
                WEAVERRUNTIME_TRY(Api::LoadEntityIndexKey(
                    transaction,
                    event.entity,
                    Api::BuiltinTypeIdToTypeId(
                        Api::BuiltinTypeId::Vector3F32),
                    &dest));

                AZ::Vector3 position = 
                    *reinterpret_cast<AZ::Vector3*>(dest);
                positionsBySubscribedEntity[event.entity.descriptor->id] = 
                    position;

                break;
            }
        case Api::ChangeListAction::Remove:
            {
                /**
                 * Load the position when the entity is removed.
                 */
                AZ::Vector3 position = positionsBySubscribedEntity[
                    event.entity.descriptor->id];

                /**
                 * Do something with position...
                 */
                break;
            }
        }
    }
    
    /* ... */
}
```

# 엔터티에 대한 위치 저장
<a name="working-with_app-sdk_ent_store-position"></a>

정수 데이터 구조를 사용하여 엔터티의 위치를 저장(상태 패브릭에 쓰기)할 수 있습니다. 해당 예제에는 다음 함수가 사용됩니다.

```
Result<void> StoreEntityIndexKey(
    Transaction& txn, 
    const Entity& entity, 
    TypeId keyTypeId, 
    std::int8_t* src, 
    std::size_t length)
```

**참고**  
다음 예제에서와 같이 `Api::BuiltinTypeId::Vector3F32`를 `Api::StoreEntityIndexKey()`에 제공해야 합니다.

**Example 배열을 사용하여 위치를 나타내는 예제**  

```
Result<void> SetEntityPositionByFloatArray(
    Api::Entity& entity, 
    Transaction& transaction)
{
    std::array<float, 3> position = { /* x */ 25, /* y */ 21, /* z */ 0 };
    
    auto* src = reinterpret_cast<std::int8_t*>(position.data());
    std::size_t length = sizeof(position);
    
    WEAVERRUNTIME_TRY(Api::StoreEntityIndexKey(
        transaction,
        entity,
        Api::BuiltinTypeIdToTypeId(Api::BuiltinTypeId::Vector3F32),
        src,
        length));
}
```

**Example struct을 사용하여 위치를 나타내는 예제**  

```
struct Position 
{
   float x;
   float y;
   float z;
};

Result<void> SetEntityPositionByStruct(
    Api::Entity& entity, 
    Transaction& transaction)
{
    Position position = { /* x */ 25, /* y */ 21, /* z */ 0 };
    
    auto* src = reinterpret_cast<std::int8_t*>(&position);
    std::size_t length = sizeof(position);
    
    WEAVERRUNTIME_TRY(Api::StoreEntityIndexKey(
        transaction,
        entity,
        Api::BuiltinTypeIdToTypeId(Api::BuiltinTypeId::Vector3F32),
        src,
        length));
}
```

# 엔터티에 대한 위치 로드
<a name="working-with_app-sdk_ent_load-position"></a>

정수 데이터 구조를 사용하여 엔터티의 위치를 로드(상태 패브릭에서 읽기)할 수 있습니다. 해당 예제에는 다음 함수가 사용됩니다.

**참고**  
다음 예제에서와 같이 `Api::BuiltinTypeId::Vector3F32`를 `Api::LoadEntityIndexKey()`에 제공해야 합니다.

**Example 배열을 사용하여 위치를 나타내는 예제**  

```
Result<void> GetEntityPosition(Api::Entity& entity, 
    Transaction& transaction)
{
    std::int8_t* dest = nullptr;
    
    WEAVERRUNTIME_TRY(Aws::WeaverRuntime::Api::LoadEntityIndexKey(
        transaction,
        entity,
        Api::BuiltinTypeIdToTypeId(
            Aws::WeaverRuntime::Api::BuiltinTypeId::Vector3F32),
        &dest));
        
    std::array<float, 3> position = 
        *reinterpret_cast<std::array<float, 3>*>(dest);
}
```

**Example struct을 사용하여 위치를 나타내는 예제**  

```
struct Position 
{struct
   float x;
   float y;
   float z;
};

Result<void> GetEntityPosition(Api::Entity& entity, Transaction& transaction)
{
    std::int8_t* dest = nullptr;
    
    WEAVERRUNTIME_TRY(Aws::WeaverRuntime::Api::LoadEntityIndexKey(
        transaction,
        entity,
        Api::BuiltinTypeIdToTypeId(
            Aws::WeaverRuntime::Api::BuiltinTypeId::Vector3F32),
        &dest));
        
    Position position = *reinterpret_cast<Position*>(dest);
}
```

# 엔터티 이벤트
<a name="working-with_app-sdk_events"></a>

 SimSpace Weaver 앱 SDK에서 다음 함수를 사용하여 모든 소유권 및 구독 이벤트를 가져올 수 있습니다.
+ `Result<OwnershipChangeList> OwnershipChanges(Transaction& txn) `
+ `Result<SubscriptionChangeList> AllSubscriptionEvents(Transaction& txn) `

콜백 기반 엔터티 이벤트 처리가 필요한 경우 SimSpace Weaver 데모 프레임워크를 사용할 수 있습니다. 자세한 내용은 다음 헤더 파일을 참조하세요.
+ `sdk-folder/packaging-tools/samples/ext/DemoFramework/include/DemoFramework/EntityEventProcessor.h`

고유한 엔터티 이벤트 처리를 만들 수도 있습니다.

**Topics**
+ [소유한 엔터티에 대한 이벤트 반복 실행](working-with_app-sdk_events_own.md)
+ [구독한 엔터티의 이벤트 반복 실행](working-with_app-sdk_events_sub.md)
+ [엔터티에 대한 소유권 변경 이벤트 반복 실행](working-with_app-sdk_events_change.md)

# 소유한 엔터티에 대한 이벤트 반복 실행
<a name="working-with_app-sdk_events_own"></a>

`OwnershipChanges()`를 사용하여 소유한 엔터티(앱 소유권 영역에 있는 엔터티)에 대한 이벤트 목록을 가져옵니다. 함수의 서명은 다음과 같습니다.

```
Result<OwnershipChangeList> OwnershipChanges(Transaction& txn)
```

그런 후 다음 예제와 같이 루프를 사용하여 엔터티를 반복합니다.

**Example 예제**  

```
WEAVERRUNTIME_TRY(Result<Api::OwnershipChangeList> ownershipChangesResult, Api::OwnershipChanges(transaction));

for (const Api::OwnershipChange& event : ownershipChangeList.changes)
{
    Api::Entity entity = event.entity;
    Api::ChangeListAction action = event.action;

    switch (action)
    {
    case Api::ChangeListAction::None:
        // insert code to handle the event
        break;
    case Api::ChangeListAction::Remove:
        // insert code to handle the event
        break;
    case Api::ChangeListAction::Add:
        // insert code to handle the event
        break;
    case Api::ChangeListAction::Update:
        // insert code to handle the event
        break;
    case Api::ChangeListAction::Reject:
        // insert code to handle the event
        break;
    }
}
```

**이벤트 유형**
+ `None` - 엔터티가 해당 영역에 있고 위치 및 필드 데이터가 수정되지 않았습니다.
+ `Remove` - 엔터티가 영역에서 제거되었습니다.
+ `Add` - 엔터티가 영역에 추가되었습니다.
+ `Update` - 엔터티가 해당 영역에 있으며 수정되었습니다.
+ `Reject` - 앱이 해당 영역에서 엔터티를 제거하지 못했습니다.

**참고**  
`Reject` 이벤트가 발생한 경우 앱은 다음 틱에서 전송을 다시 시도합니다.

# 구독한 엔터티의 이벤트 반복 실행
<a name="working-with_app-sdk_events_sub"></a>

`AllSubscriptionEvents()`를 사용하여 구독한 엔터티(앱 구독 영역에 있는 엔터티)에 대한 이벤트 목록을 가져옵니다. 함수의 서명은 다음과 같습니다.

```
Result<SubscriptionChangeList> AllSubscriptionEvents(Transaction& txn)
```

그런 후 다음 예제와 같이 루프를 사용하여 엔터티를 반복합니다.

**Example 예제**  

```
WEAVERRUNTIME_TRY(Api::SubscriptionChangeList subscriptionChangeList, Api::AllSubscriptionEvents(transaction));

for (const Api::SubscriptionEvent& event : subscriptionChangeList.changes)
{
    Api::Entity entity = event.entity;
    Api::ChangeListAction action = event.action;

    switch (action)
    {
    case Api::ChangeListAction::None:
        // insert code to handle the event
        break;
    case Api::ChangeListAction::Remove:
        // insert code to handle the event
        break;
    case Api::ChangeListAction::Add:
        // insert code to handle the event
        break;
    case Api::ChangeListAction::Update:
        // insert code to handle the event
        break;
    case Api::ChangeListAction::Reject:
        // insert code to handle the event
        break;
    }
}
```

**이벤트 유형**
+ `None` - 엔터티가 해당 영역에 있고 위치 및 필드 데이터가 수정되지 않았습니다.
+ `Remove` - 엔터티가 영역에서 제거되었습니다.
+ `Add` - 엔터티가 영역에 추가되었습니다.
+ `Update` - 엔터티가 해당 영역에 있으며 수정되었습니다.
+ `Reject` - 앱이 해당 영역에서 엔터티를 제거하지 못했습니다.

**참고**  
`Reject` 이벤트가 발생한 경우 앱은 다음 틱에서 전송을 다시 시도합니다.

# 엔터티에 대한 소유권 변경 이벤트 반복 실행
<a name="working-with_app-sdk_events_change"></a>

엔터티가 소유권 영역과 구독 영역 사이를 이동하는 이벤트를 가져오려면 현재 및 이전 엔터티 소유권 및 구독 이벤트 간의 변경 사항을 비교합니다.

다음을 읽으면 이러한 이벤트를 처리할 수 있습니다.
+ `Api::SubscriptionChangeList`
+ `Api::OwnershipEvents`

그런 다음 변경 내용을 이전에 저장된 데이터와 비교할 수 있습니다.

다음 예제에서는 엔터티 소유권 변경 이벤트를 처리하는 방법을 보여줍니다. 이 예제에서는 구독 엔터티와 소유 엔터티 사이를 전환하는 엔터티의 경우 (어느 방향으로든) 소유권 제거/추가 이벤트가 먼저 발생하고 다음 틱에서 구독 제거/추가 이벤트가 발생한다고 가정합니다.

**Example 예제**  

```
Result<void> ProcessOwnershipEvents(Transaction& transaction)
{
    using EntityIdsByAction =
        std::unordered_map<Api::ChangeListAction, 
        std::vector<Api::EntityId>>;
    using EntityIdSetByAction =
        std::unordered_map<Api::ChangeListAction, 
        std::unordered_set<Api::EntityId>>;
   
    static EntityIdsByAction m_entityIdsByPreviousOwnershipAction;
    
    EntityIdSetByAction entityIdSetByAction;
   
    /**
     * Enumerate Api::SubscriptionChangeList items 
     * and store Add and Remove events.
     */ 
    WEAVERRUNTIME_TRY(Api::SubscriptionChangeList subscriptionEvents, 
        Api::AllSubscriptionEvents(transaction));
   
    for (const Api::SubscriptionEvent& event : subscriptionEvents.changes)
    {
        const Api::ChangeListAction action = event.action;
    
        switch (action)
        {
        case Api::ChangeListAction::Add:
        case Api::ChangeListAction::Remove:
    
            {
                entityIdSetByAction[action].insert(
                    event.entity.descriptor->id);
                break;
            }
        case Api::ChangeListAction::None:
        case Api::ChangeListAction::Update:
        case Api::ChangeListAction::Reject:
            {
                break;
            }
        }
    }
    
    EntityIdsByAction entityIdsByAction;
    
    /**
     * Enumerate Api::OwnershipChangeList items 
     * and store Add and Remove events.
     */
    
    WEAVERRUNTIME_TRY(Api::OwnershipChangeList ownershipChangeList, 
        Api::OwnershipChanges(transaction));
   
    for (const Api::OwnershipChange& event : ownershipChangeList.changes)
    {
        const Api::ChangeListAction action = event.action;
    
        switch (action)
        {
        case Api::ChangeListAction::Add:
        case Api::ChangeListAction::Remove:
            {
                entityIdsByAction[action].push_back(
                    event.entity.descriptor->id);
                break;
            }
        case Api::ChangeListAction::None:
        case Api::ChangeListAction::Update:
        case Api::ChangeListAction::Reject:
            {
                break;
            }
        }
    
    }
      
    std::vector<Api::EntityId> fromSubscribedToOwnedEntities;
    std::vector<Api::EntityId> fromOwnedToSubscribedEntities;
   
    /**
     * Enumerate the *previous* Api::OwnershipChangeList Remove items
     * and check if they are now in 
     * the *current* Api::SubscriptionChangeList Add items.
     *
     * If true, then that means 
     * OnEntityOwnershipChanged(bool isOwned = false)
     */ 
    for (const Api::EntityId& id : m_entityIdsByPreviousOwnershipAction[
        Api::ChangeListAction::Remove])
    {
        if (entityIdSetBySubscriptionAction[
            Api::ChangeListAction::Add].find(id) !=
                entityIdSetBySubscriptionAction[
                Api::ChangeListAction::Add].end())
        {
            fromOwnedToSubscribedEntities.push_back(id);
        }
    }
    
   
    /**
     * Enumerate the *previous* Api::OwnershipChangeList Add items
     * and check if they are now in 
     * the *current* Api::SubscriptionChangeList Remove items.
     *
     * If true, then that means 
     * OnEntityOwnershipChanged(bool isOwned = true)
     */ 
    for (const Api::EntityId& id : m_entityIdsByPreviousOwnershipAction[
        Api::ChangeListAction::Add])
    {
        if (entityIdSetBySubscriptionAction[
            Api::ChangeListAction::Remove].find(id) !=
            
                entityIdSetBySubscriptionAction[
                Api::ChangeListAction::Remove].end())
        {
            fromSubscribedToOwnedEntities.push_back(id);
        }
    }
    
    m_entityIdsByPreviousOwnershipAction = entityIdsByOwnershipAction;
    
    return Success();
}
```

# Result 및 오류 처리
<a name="working-with_app-sdk_result"></a>

`Aws::WeaverRuntime::Result<T>` 클래스는 타사 `Outcome` 라이브러리를 사용합니다. 다음 패턴을 사용하여 `Result`를 확인하고 API 직접 호출에서 반환된 오류를 캐치할 수 있습니다.

```
void DoBeginUpdate(Application& app)
{
    Result<Transaction> transactionResult = Api::BeginUpdate(app);
    
    if (transactionResult)
    {
        Transaction transaction = 
            std::move(transactionResult).assume_value();
        
        /**
         * Do things with transaction ...
         */
    }
    else
    {     
        ErrorCode errorCode = WEAVERRUNTIME_EXPECT_ERROR(transactionResult);
        /**
         * Macro compiles to:
         * ErrorCode errorCode = transactionResult.assume_error();
         */
    }
}
```

## Result 제어문 매크로
<a name="working-with_app-sdk_result_macro"></a>

`Aws::WeaverRuntime::Result<T>` 반환 유형이 있는 함수 내에서 이전 코드 패턴 대신 `WEAVERRUNTIME_TRY` 매크로를 사용할 수 있습니다. 매크로는 전달된 함수를 실행합니다. 전달된 함수가 실패하면 매크로는 둘러싸는 함수가 오류를 반환하도록 합니다. 전달된 함수가 성공하면 실행은 다음 줄로 진행됩니다. 다음 예제에서는 이전 `DoBeginUpdate()` 함수의 재작성을 보여줍니다. 이 버전에서는 if-else 제어 구조 대신 `WEAVERRUNTIME_TRY` 매크로를 사용합니다. 함수의 반환 형식은 `Aws::WeaverRuntime::Result<void>`입니다.

```
Aws::WeaverRuntime::Result<void> DoBeginUpdate(Application& app)
{
    /**
     * Execute Api::BeginUpdate() 
     * and return from DoBeginUpdate() if BeginUpdate() fails.
     * The error is available as part of the Result.
     */
    WEAVERRUNTIME_TRY(Transaction transaction, Api::BeginUpdate(m_app));
    
    /**
     * Api::BeginUpdate executed successfully.
     *
     * Do things here.
     */
    
    return Aws::Success();
}
```

`BeginUpdate()`가 실패하면 매크로는 실패와 `DoBeginUpdate()`를 조기에 반환합니다. `WEAVERRUNTIME_EXPECT_ERROR` 매크로를 사용하여 `BeginUpdate()`에서 `Aws::WeaverRuntime::ErrorCode`를 가져올 수 있습니다. 다음 예제는 `Update()` 함수의 `DoBeginUpdate()` 호출 및 실패 시 오류 코드를 가져오는 방법을 보여줍니다.

```
void Update(Application& app)
{
    Result<void> doBeginUpdateResult = DoBeginUpdate(app);
    
    if (doBeginUpdateResult)
    {
        /**
         * Successful.
         */
    }
    else
    {    
        /**
         * Get the error from Api::BeginUpdate().
         */ 
        ErrorCode errorCode = WEAVERRUNTIME_EXPECT_ERROR(doBeginUpdateResult);

    }
}
```

`Update()`의 반환 유형을 `Aws::WeaverRuntime::Result<void>`로 변경하여 `Update()`를 호출하는 함수에서 `BeginUpdate()`의 오류 코드를 사용할 수 있도록 할 수 있습니다. 이 프로세스를 반복하여 호출 스택 아래쪽으로 오류 코드를 계속 보낼 수 있습니다.

# 일반 및 도메인 유형
<a name="working-with_app-sdk_generics"></a>

 SimSpace Weaver 앱 SDK는 단일 정밀도 데이터 형식 `Api::Vector2F32` 및 `Api::BoundingBox2F32`, 이중 정밀도 `Api::Vector2F64` 및를 제공합니다`Api::BoundingBox2F64`. 이러한 데이터 유형은 편리한 방법이 없는 수동적 데이터 구조입니다. 참고로 API는 `Api::Vector2F32` 및 `Api::BoundingBox2F32`만 사용합니다. 이러한 데이터 유형을 사용하여 구독을 만들고 수정할 수 있습니다.

 SimSpace Weaver 데모 프레임워크는 `Vector3` 및 `Aabb`를 포함하는 수AzCore학 라이브러리의 최소 버전을 제공합니다. 자세한 내용은 다음의 파일 헤더를 참조하세요.
+ `sdk-folder/packaging-tools/samples/ext/DemoFramework/include/AzCore/Math`

# 기타 앱 SDK 작업
<a name="working-with_app-sdk_misc"></a>

**Topics**
+ [AllSubscriptionEvents 및 OwnershipChanges에는 마지막 호출의 이벤트가 포함됨](working-with_app-sdk_misc_events-from-last-call.md)
+ [SubscriptionChangeList 처리 후 읽기 잠금 해제](working-with_app-sdk_misc_release-locks.md)
+ [테스트용 독립 실행형 앱 인스턴스 생성](working-with_app-sdk_misc_testing-app.md)

# AllSubscriptionEvents 및 OwnershipChanges에는 마지막 호출의 이벤트가 포함됨
<a name="working-with_app-sdk_misc_events-from-last-call"></a>

`Api::AllSubscriptionEvents()` 및 `Api::OwnershipChanges()`에 대한 호출의 반환 값에는 **마지막 틱이 아닌** 마지막 호출의 이벤트가 포함됩니다. 다음 예제에서는 첫 번째 호출 후 해당 함수가 즉시 호출되기 때문에 `secondSubscriptionEvents` 및 `secondOwnershipChangeList`가 비어 있습니다.

10개의 틱을 기다린 다음 `Api::AllSubscriptionEvents()` 및 `Api::OwnershipChanges()`를 호출하면 해당 결과에 마지막 틱이 아닌 최근 10개 틱의 이벤트와 변경 내용이 포함됩니다.

**Example 예제**  

```
Result<void> ProcessOwnershipChanges(Transaction& transaction)
{
    WEAVERRUNTIME_TRY(
        Api::SubscriptionChangeList firstSubscriptionEvents,
        Api::AllSubscriptionEvents(transaction));
    WEAVERRUNTIME_TRY(
        Api::OwnershipChangeList firstOwnershipChangeList,
        Api::OwnershipChanges(transaction));
    
    WEAVERRUNTIME_TRY(
        Api::SubscriptionChangeList secondSubscriptionEvents,
        Api::AllSubscriptionEvents(transaction));
    WEAVERRUNTIME_TRY(
        Api::OwnershipChangeList secondOwnershipChangeList,
        Api::OwnershipChanges(transaction));
    
    /**
     * secondSubscriptionEvents and secondOwnershipChangeList are 
     * both empty because there are no changes since the last call.
     */
}
```

**참고**  
`AllSubscriptionEvents()` 함수는 구현되었지만 `SubscriptionEvents()` 함수는 **구현되지 않았습니다**.

# SubscriptionChangeList 처리 후 읽기 잠금 해제
<a name="working-with_app-sdk_misc_release-locks"></a>

업데이트를 시작하면 이전 틱의 다른 파티션에 커밋된 데이터에 대한 공유 메모리 세그먼트가 생깁니다. 독자는 이러한 공유 메모리 세그먼트를 잠글 수 있습니다. 모든 독자가 잠금을 해제하기 전까지는 앱을 완전히 커밋할 수 없습니다. 최적화를 위해 앱은 `Api::SubscriptionChangelist` 항목을 처리한 후 잠금을 해제하도록 `Api::ReleaseReadLeases()`를 호출해야 합니다. 그러면 커밋 시 경합이 줄어듭니다. `Api::Commit()`은 기본적으로 읽기 임대를 해제하지만 구독 업데이트를 처리한 후 읽기 임대를 수동으로 릴리스하는 것이 가장 좋습니다.

**Example 예제**  

```
Result<void> ProcessSubscriptionChanges(Transaction& transaction)
{
    WEAVERRUNTIME_TRY(ProcessSubscriptionChanges(transaction));
    
    /**
     * Done processing Api::SubscriptionChangeList items.
     * Release read locks. 
     */
        
    WEAVERRUNTIME_EXPECT(Api::ReleaseReadLeases(transaction));
    
    ...
}
```

# 테스트용 독립 실행형 앱 인스턴스 생성
<a name="working-with_app-sdk_misc_testing-app"></a>

`Api::CreateStandaloneApplication()`을 통해 실제 시뮬레이션에서 코드를 실행하기 전에 독립 실행형 앱을 생성하여 앱 로직을 테스트할 수 있습니다.

**Example 예제**  

```
int main(int argc, char* argv[])
{
    Api::StandaloneRuntimeConfig config = { 
        /* run_for_seconds (the lifetime of the app) */ 3,
        /* tick_hertz (the app clock rate) */ 10 };
    
    Result<Application> applicationResult =
        Api::CreateStandaloneApplication(config);

    ...
}
```