

终止支持通知：2026 年 5 月 20 日， AWS 将终止对的支持。 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)。

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 与... 合作 SimSpace Weaver
<a name="working-with"></a>

本章提供帮助您在 SimSpace Weaver中构建应用程序的信息和指导。

**Topics**
+ [配置模拟](working-with_configuring-simulation.md)
+ [模拟的最长持续时间](working-with_max-duration.md)
+ [开发应用程序](working-with_developing-apps.md)
+ [开发客户端应用程序](working-with_developing-client-applications.md)
+ [获取定制化应用程序的 IP 地址和端口号](working-with_get-ip.md)
+ [启动虚幻引擎视图客户端](working-with_unreal-client.md)
+ [当地发展 SimSpace Weaver](working-with_local-development.md)
+ [AWS SimSpace Weaver 应用程序 SDK](working-with_app-sdk.md)
+ [AWS SimSpace Weaver 演示框架](working-with_demo-framework.md)
+ [使用服务限额](working-with_quotas.md)
+ [调试模拟](working-with_debugging.md)
+ [自定义容器](working-with_custom-containers.md)
+ [使用 Python](working-with_python.md)
+ [对其他引擎的支持](working-with_engines.md)
+ [将许可软件用于 AWS SimSpace Weaver](working-with_byol.md)
+ [使用管理您的资源 AWS CloudFormation](working-with_cloudformation.md)
+ [快照](working-with_snapshots.md)
+ [消息收发](working-with_messaging.md)

# 配置模拟
<a name="working-with_configuring-simulation"></a>

**模拟架构**（或**架构**）是 YAML格式的文本文件，用于指定模拟的配置。您可以使用相同的架构启动多个模拟。架构文件位于模拟的项目文件夹中。您可以使用任何文本编辑器来编辑文件。 SimSpace Weaver 仅在启动模拟时读取您的架构。您对架构文件所做的任何编辑只会影响在编辑后启动的新模拟。

要配置模拟，请编辑您的仿真架构文件（使用适合您的操作系统的路径分隔符）：

```
project-folder\tools\project-name-schema.yaml
```

在创建新模拟时上传模拟架构。在构建模拟的过程中，项目的快速入门帮助程序脚本将上传架构：

```
project-folder\tools\windows\quick-start.py
```

有关运行快速入门脚本的更多信息，请参阅本指南一[入门](getting-started.md)章[详细教程](getting-started_detailed.md)中的。

## 模拟配置参数
<a name="working-with_configuring-simulation_config-parameters"></a>

模拟架构包含引导信息，包括：
+ **模拟属性** – SDK 版本和计算配置（[工作线程](w2aac51.md#glossary_worker)的类型和数量）
+ **时钟** – 刻度率和容限
+ **空间分区策略** – 空间拓扑（例如网格）、边界和置放组（工作线程上的空间分区分组）
+ **域及其应用程序** – 应用程序存储桶、路径和启动命令

SimSpace Weaver 使用您的架构配置来配置和排列空间分区、启动应用程序以及以您指定的滴答率推进模拟。

**注意**  
 SimSpace Weaver 应用程序 SDK 中的创建项目脚本将根据示例应用程序自动为您生成仿真架构。

以下主题介绍模拟架构中的参数。有关模拟架构的完整说明，请参阅[SimSpace Weaver 仿真架构参考](schema-reference.md)。

**Topics**
+ [模拟配置参数](#working-with_configuring-simulation_config-parameters)
+ [SDK 版本](working-with_configuring-simulation_sdk-version.md)
+ [模拟属性](working-with_configuring-simulation_simulation-properties.md)
+ [工作线程](working-with_configuring-simulation_workers.md)
+ [时钟](working-with_configuring-simulation_clock.md)
+ [分区策略](working-with_configuring-simulation_partitioning-strategies.md)
+ [域](working-with_configuring-simulation_domains.md)

# SDK 版本
<a name="working-with_configuring-simulation_sdk-version"></a>

该`sdk_version`字段指定架构 SimSpace Weaver 的格式化版本。有效值：`1.17`、`1.16`、`1.15`、`1.14`、`1.13`、`1.12`

**重要**  
`sdk_version` 的值仅包括主版本号和第一个次要版本号。例如，值 `1.12` 指定所有版本 `1.12.x`，例如 `1.12.0`、`1.12.1`、和 `1.12.2`。

# 模拟属性
<a name="working-with_configuring-simulation_simulation-properties"></a>

架构的 `simulation_properties` 部分为实体的索引字段（通常是空间位置）指定日志配置和数据类型。

```
simulation_properties:
  log_destination_service: "logs"
  log_destination_resource_name: "MySimulationLogs"
  default_entity_index_key_type: "Vector3<f32>"
```

`log_destination_service` 的值决定对 `log_destination_resource_name` 的值的解释。目前，唯一支持的值是 `logs`。这意味着，的值`log_destination_resource_name`是 Amazon CloudWatch 日志中日志组的名称

**注意**  
日志记录是可选的。如果您未配置日志目标属性，则您的模拟将不会生成日志。

`default_entity_index_key_type` 属性为必需属性。唯一有效值为 `Vector3<f32>`。

# 工作线程
<a name="working-with_configuring-simulation_workers"></a>

该`workers`部分指定了您想要进行模拟的工作人员的类型和数量。 SimSpace Weaver 使用自己的工作线程类型，这些类型映射到 Amazon EC2 实例类型。

```
workers:
  MyComputeWorkers:
    type: "sim.c5.24xlarge"
    desired: 1
```

## 启用多工作线程模拟
<a name="working-with_configuring-simulation_workers_multi-worker"></a>

您可以创建使用多个工作线程的模拟。默认情况下，模拟使用 1 个工作线程。在开始模拟之前，您必须修改模拟架构。

**注意**  
您无法更改已经开始的模拟。如果要为正在运行的模拟启用多工作线程，必须先停止并删除模拟。

要使用多个工作线程，请将计算实例的 `desired` 数量设置为大于 1 的值。每个工作线程支持的应用程序数量有上限。有关更多信息，请参阅[SimSpace Weaver 端点和配额](service-quotas.md)。 SimSpace Weaver 只有当工作线程上的应用程序数量超过此限制时，才会使用 1 个以上的工作线程。 SimSpace Weaver 可以将应用程序放置在任何可用的工作程序上。无法保证将应用程序放置在特定工作线程上。

以下架构片段演示了请求 2 个工作线程的模拟的配置。如果应用程序的数量超过 1 个工作线程支持的最大应用程序数量， SimSpace Weaver 会尝试分配第二个工作线程。

```
workers:
  MyComputeWorkers:
    type: "sim.c5.24xlarge"
    desired: 2
```

# 时钟
<a name="working-with_configuring-simulation_clock"></a>

`clock` 部分指定模拟时钟的属性。目前，您只能配置**刻度率**（时钟每秒发送给应用程序的时钟周期数）。刻度率是最大速率。有效刻度率可能会更低，因为在下一个时钟周期开始之前，所有客户的操作（如实体更新）都必须完成。刻度率也称为**时钟频率**。

`tick_rate` 的有效值取决于架构中 `sdk_version` 指定的值。

**刻度率的有效值**
+ 低于 `"1.14"` 的版本：
  + `10`
  + `15`
  + `30`
+ 版本 `"1.14"` 或更高版本：
  + `"10"`
  + `"15"`
  + `"30"`
  + `"unlimited"`

    有关更多信息，请参阅 [无限制刻度率](#working-with_configuring-simulation_clock_unlimited)。

**重要**  
对于低于 `"1.14"` 的 `sdk_version`，`tick_rate` 的值为**整数**，例如 `30`。
对于 `sdk_version` 为 `"1.14"` 或更高版本的架构，`tick_rate` 的值是一个**字符串**，例如 `"30"`。该值**必须包含在双引号内**。  
如果将版本 `"1.12"` 或 `"1.13"` 的架构转换为版本 `"1.14"` 或更高版本，则必须将 `tick_rate` 的值包含在双引号中。

## 无限制刻度率
<a name="working-with_configuring-simulation_clock_unlimited"></a>

您可以将 `tick_rate` 设置为 `"unlimited"`，以便让模拟的运行速度与代码的执行速度一样快。使用无限的报价率，在所有应用程序完成当前报价的提交后立即 SimSpace Weaver 发送下一个报价。

**重要**  
1.14.0 之前的 SimSpace Weaver 版本不支持无限滴答率。架构中 `sdk_version` 的最小值为 `"1.14"`。

**SimSpace Weaver Local 中的无限制刻度率**  
SimSpace Weaver Local 实现 `"unlimited"` 时就像架构指定 10 kHz (10000) 的刻度率一样。其效果与在 AWS 云中指定无限制刻度率相同。您仍然可以在架构指定 `tick_rate: "unlimited"`。有关 SimSpace Weaver Local的更多信息，请参阅[当地发展 SimSpace Weaver](working-with_local-development.md)。

## 有关时钟的常见问题解答
<a name="working-with_configuring-simulation_clock_faq"></a>

### 问题 1：我能否将 STARTED 模拟更改为使用不同的刻度率？
<a name="working-with_configuring-simulation_clock_faq_q1"></a>

对于在生命周期的任何阶段已存在于 AWS 云 中的模拟，您都无法更改刻度率。您也无法更改正在 SimSpace Weaver Local 中运行的模拟的刻度率。您可以在架构中设置 `tick_rate`，然后从该架构启动新的模拟。

### 问题 2：我能否在 1.14 之前的版本中以无限制的刻度率运行模拟？
<a name="working-with_configuring-simulation_clock_faq_q2"></a>

不能，1.14.0 之前的版本不支持无限制的刻度率。

## 排除时钟错误
<a name="working-with_configuring-simulation_clock_troubleshooting"></a>

如果您的模拟无法启动，则可以在 **DescribeSimulation**API 的输出`"StartError"`中检查的值。架构中的无效的 `tick_rate` 值将产生以下错误。

**注意**  
为了提高可读性，此处显示的错误输出以多行显示。实际错误输出只有一行。
+ 低于 `"1.14"` 的 `sdk_version` 和 `tick_rate` 的值是无效整数。有效值：`10`、`15`、`30`

  ```
  "[{\"errorType\":\"SchemaFormatInvalid\",\"errorMessage\":
      \"$.clock.tick_rate: does not have a value in the enumeration [10, 15, 30]\"}]"
  ```
+ 低于 `"1.14"` 的 `sdk_version` 和 `tick_rate` 的值是字符串。有效值：`10`、`15`、`30`

  ```
  "[{\"errorType\":\"SchemaFormatInvalid\",\"errorMessage\":
      \"$.clock.tick_rate: does not have a value in the enumeration [10, 15, 30]\"},
      {\"errorType\":\"SchemaFormatInvalid\",
      \"errorMessage\":\"$.clock.tick_rate: string found, integer expected\"}]"
  ```
+ `sdk_version` 是 `"1.14"` 或更高版本，`tick_rate` 的值是无效字符串。有效值：`"10"`、`"15"`、`"30"`、`"unlimited"`

  ```
  "[{\"errorType\":\"SchemaFormatInvalid\",\"errorMessage\":
      \"$.clock.tick_rate: does not have a value in the enumeration [10, 15, 30, unlimited]\"}]"
  ```
+ `sdk_version` 是 `"1.14"` 或更高版本，`tick_rate` 的值是整数。有效值：`"10"`、`"15"`、`"30"`、`"unlimited"`

  ```
  "[{\"errorType\":\"SchemaFormatInvalid\",\"errorMessage\":
      \"$.clock.tick_rate: does not have a value in the enumeration [10, 15, 30, unlimited]\"},
      {\"errorType\":\"SchemaFormatInvalid\",
      \"errorMessage\":\"$.clock.tick_rate: integer found, string expected\"}]"
  ```

# 分区策略
<a name="working-with_configuring-simulation_partitioning-strategies"></a>

`partitioning_strategies` 部分指定了空间应用程序分区的配置属性。您可以为分区策略（本部分的一组属性）提供一个名称，并在空间应用程序配置中使用该名称。

```
partitioning_strategies:
  MyGridPartitioning:
    topology: "Grid"
    aabb_bounds:
      x: [0, 1000]
      y: [0, 1000]
    grid_placement_groups:
      x: 1
      y: 1
```

`topology` 属性指定模拟使用的坐标系类型。值 `Grid` 指定一个二维 (2D) 网格。

对于`Grid`拓扑，仿真空间被建模为轴对齐的边界框 (AABB)。 您可以在属性中指定 AABB 的每个轴的坐标边界。`aabb_bounds`模拟中空间上存在的所有实体都必须在 AABB 中一个位置。

## 网格置放群组
<a name="working-with_configuring-simulation_partitioning-strategies_placement-groups"></a>

**置放群组**是您 SimSpace Weaver 要放置在同一个工作器上的空间应用程序分区的集合。您可以在`grid_placement_groups`属性中指定置放群组（在网格中）的数量和排列方式。 SimSpace Weaver 将尝试在置放群组之间均匀分配分区。同一置放群组中具有分区的空间应用程序的所有权区域在空间上是相邻的。

我们建议让 x \$1 y 等于所需的工作线程数量。如果不相等， SimSpace Weaver 将尝试在可用工作人员之间平衡您的安置组。

如果您未指定置放群组配置， SimSpace Weaver 将为您计算一个置放群组配置。

# 域
<a name="working-with_configuring-simulation_domains"></a>

您可以为域的一组配置属性提供一个名称。域中应用程序的启动设置决定域的类型：
+ **`launch_apps_via_start_app_call`** – 自定义域
+ **`launch_apps_by_partitioning_strategy`** – 空间域
+ **`launch_apps_per_worker`**（未包含在示例应用程序中）– 服务域

**重要**  
SimSpace Weaver 每次仿真最多支持 5 个域。这包括所有空间、自定义和服务域。

```
domains:
  MyViewDomain:
    launch_apps_via_start_app_call: {}
    app_config:
      package: "s3://weaver-myproject-111122223333-us-west-2/MyViewApp.zip"
      launch_command: ["MyViewApp"]
      required_resource_units:
        compute: 1
      endpoint_config:
        ingress_ports:
          - 7000
  MySpatialDomain:
    launch_apps_by_partitioning_strategy:
      partitioning_strategy: "MyGridPartitioning"
      grid_partition:
        x: 2
        y: 2
    app_config:
      package: "s3://weaver-myproject-111122223333-us-west-2/MySpatialApp.zip"
      launch_command: ["MySpatialApp"]
      required_resource_units:
        compute: 1
```

**注意**  
SimSpace Weaver 应用程序 SDK 版本 1.12.x 项目对应用程序.zip 文件和架构使用单独的存储桶：  
weaver-*lowercase-project-name*--ap *account-number* p-zips-*region*
weaver-*lowercase-project-name*-*account-number*-schemas-*region*

**Topics**
+ [应用程序配置](working-with_configuring-simulation_domains_app-config.md)
+ [配置空间域](working-with_configuring-simulation_domains_spatial.md)
+ [网络端点](working-with_configuring-simulation_domains_endpoints.md)
+ [配置服务域](working-with_configuring-simulation_domains_service-domains.md)

# 应用程序配置
<a name="working-with_configuring-simulation_domains_app-config"></a>

您可以将应用程序 (`app_config`) 的配置指定为其域配置的一部分。所有类型的域都使用这些相同的应用程序配置属性。

```
    app_config:
      package: "s3://weaver-myproject-111122223333-us-west-2/MyViewApp.zip"
      launch_command: ["MyViewApp"]
      required_resource_units:
        compute: 1
```

**注意**  
SimSpace Weaver 应用程序 SDK 版本 1.12.x 项目对应用程序.zip 文件和架构使用单独的存储桶：  
weaver-*lowercase-project-name*--ap *account-number* p-zips-*region*
weaver-*lowercase-project-name*-*account-number*-schemas-*region*

`package` 属性指定 S3 存储桶中 zip 文件的 S3 URI。该 zip 文件包含应用程序可执行文件（也称为*二进制文件*）及其所需的任何其他资源（例如库）。应用程序可执行文件的每个实例都在工作线程上的 Docker 容器中运行。

`launch_command` 属性指定可执行文件的名称以及用于运行该应用程序的任何命令行选项。`launch_command` 的值是一个数组。整个启动命令字符串的每个令牌都是数组中的一个元素。

**示例**
+ 对于启动命令：`MyTestApp --option1 value1`
+ 指定：`launch_command: ["MyTestApp", "-option1", "value1"]`

该`required_resource_units`属性指定 SimSpace Weaver 应分配给此应用程序的计算资源单位数。计算资源单位是工作程序上固定数量的处理容量 (vCPU) 和内存 (RAM)。您可以增加该值来提高应用程序在工作线程上运行时可用的计算能力。每个工作线程上的计算资源单位数量有限。有关更多信息，请参阅 [SimSpace Weaver 端点和配额](service-quotas.md)。

# 配置空间域
<a name="working-with_configuring-simulation_domains_spatial"></a>

对于空间域，必须指定 `partitioning_strategy`。此属性的值是您为分区策略指定的名称，该分区策略在架构的另一个部分中定义。

```
  MySpatialDomain:
    launch_apps_by_partitioning_strategy:
      partitioning_strategy: "MyGridPartitioning"
      grid_partition:
        x: 2
        y: 2
    app_config:
      package: "s3://weaver-myproject-111122223333-us-west-2/MySpatialApp.zip"
      launch_command: ["MySpatialApp"]
      required_resource_units:
        compute: 1
```

**注意**  
SimSpace Weaver 应用程序 SDK 版本 1.12.x 项目对应用程序.zip 文件和架构使用单独的存储桶：  
weaver-*lowercase-project-name*--ap *account-number* p-zips-*region*
weaver-*lowercase-project-name*-*account-number*-schemas-*region*

使用`Grid`拓扑（此版本中唯一支持的拓扑）的分区策略指示 SimSpace Weaver 在网格中排列此域的空间应用程序分区。`grid_partition` 属性指定分区网格的行数和列数。

SimSpace Weaver 将为分区网格中的每个像元启动 1 个空间应用程序实例。例如，如果空间域具有`grid_partition`值`x: 2`和`y: 2`，则该空间域中有 2 \$1 2 = 4 个分区。 SimSpace Weaver 将启动在空间域中配置的 4 个应用程序实例，并为每个应用程序实例分配 1 个分区。

**主题**
+ [空间域的资源需求](#working-with_configuring-simulation_domains_spatial_resources)
+ [多个空间域](#working-with_configuring-simulation_domains_spatial_multiple)
+ [有关空间域的常见问题解答](#working-with_configuring-simulation_domains_spatial_faq)
+ [空间域问题排查](#working-with_configuring-simulation_domains_spatial_troubleshooting)

## 空间域的资源需求
<a name="working-with_configuring-simulation_domains_spatial_resources"></a>

您可以为每个工作线程分配最多 17 个计算资源单位。您可以指定每个空间应用程序在空间域的 `app_config` 部分使用的计算资源单位的数量。

**Example 显示空间应用程序计算资源单位的示例架构片段**  

```
  MySpatialDomain:
    launch_apps_by_partitioning_strategy:
      partitioning_strategy: "MyGridPartitioning"
      grid_partition:
        x: 2
        y: 2
    app_config:
      package: "s3://weaver-myproject-111122223333-artifacts-us-west-2/MySpatialApp.zip"
      launch_command: ["MySpatialApp"]
      required_resource_units:
        compute: 1
```

要计算域所需的计算资源单位数量，请将网格中的像元数量（在 `grid_partition`中，`x` \$1 `y`）乘以分配给空间应用程序的计算资源单位数量。

在上述示例中，域 `MySpatialDomain` 指定：
+ `x`: `2`
+ `y`: `2`
+ `compute`: `1`

`MySpatialDomain` 的网格有 2 \$1 2 = 4 个像元。空间域需要 4 \$1 1 = 4 个计算资源单位。

架构中指定的所有域的计算资源单位总数必须小于或等于工作线程的数量 `desired` 乘以每个工作线程的最大计算资源单位数量 (17)。

## 多个空间域
<a name="working-with_configuring-simulation_domains_spatial_multiple"></a>

您可以将模拟配置为使用多个的空间域。例如，您可以使用 1 个空间域来控制模拟中的主角色（例如，人和汽车），同时使用不同的空间域来控制环境。

您也可以使用多个空间域为模拟的不同部分分配不同的资源。例如，如果您的模拟中有一种类型的实体的实例数量比其他类型多 10 倍，则可以创建不同的域来处理各种实体类型，并为具有更多实体的域分配更多资源。

**重要**  
SimSpace Weaver 1.14.0 之前的版本不支持多个空间域。

**重要**  
AWS SimSpace Weaver Local目前不支持多个空间域。有关 SimSpace Weaver Local的更多信息，请参阅[当地发展 SimSpace Weaver](working-with_local-development.md)。

**重要**  
SimSpace Weaver 每次仿真最多支持 5 个域。这包括所有空间、自定义和服务域。

### 配置多个空间域
<a name="working-with_configuring-simulation_domains_spatial_multiple_configure"></a>

要配置多个空间域，请将其他空间域定义作为单独的命名部分添加到架构中。每个域都必须指定 `launch_apps_by_partitioning_strategy` 密钥。请参阅以下示例架构。

```
sdk_version: "1.14"
workers:
  MyComputeWorkers:
    type: "sim.c5.24xlarge"
    desired: 1
clock:
  tick_rate: "30"
partitioning_strategies:
  MyGridPartitioning:
    topology: Grid
    aabb_bounds:
      x: [0, 1000]
      y: [0, 1000]
domains:
  MySpatialDomain:
    launch_apps_by_partitioning_strategy:
      partitioning_strategy: "MyGridPartitioning"
      grid_partition:
        x: 2
        y: 2
    app_config:
      package: "s3://weaver-myproject-111122223333-artifacts-us-west-2/MySpatialApp.zip"
      launch_command: ["MySpatialApp"]
      required_resource_units:
        compute: 1
  MySecondSpatialDomain:
    launch_apps_by_partitioning_strategy:
      partitioning_strategy: "MyGridPartitioning"
      grid_partition:
        x: 2
        y: 2
    app_config:
      package: "s3://weaver-myproject-111122223333-artifacts-us-west-2/MySpatialApp2.zip"
      launch_command: ["MySpatialApp2"]
      required_resource_units:
        compute: 1
```

### 将空间域放在一起
<a name="working-with_configuring-simulation_domains_spatial_multiple_placement"></a>

在某些情况下，您可能需要将空间域的分区放在另一个域的分区旁。如果这些分区相互创建跨域订阅，则可以改善性能特征。

将顶级密钥`placement_constraints`添加到架构中，以指定 SimSpace Weaver 应将哪些域放在一起。所需的 `on_workers` 键必须引用架构中名为 `workers` 的配置。

**Example 显示放置在一起的空间域的示例架构片段**  

```
workers:
  MyComputeWorkers:
    type: "sim.c5.24xlarge"
    desired: 2
placement_constraints:
  - placed_together: ["MySpatialDomain", "MySecondSpatialDomain"]
    on_workers: ["MyComputeWorkers"]
```

**重要**  
如果您使用置放群组：  
确保 x \$1 y 是工作线程数量的倍数。
确保置放群组的值是放置在一起的域的网格维度的公因数。
如果您**不使用**置放群组：  
确保空间域网格的 1 个轴具有等于工作线数量的公因数。
有关置放群组的更多信息，请参阅[分区策略](working-with_configuring-simulation_partitioning-strategies.md#working-with_configuring-simulation_partitioning-strategies_placement-groups)。

## 有关空间域的常见问题解答
<a name="working-with_configuring-simulation_domains_spatial_faq"></a>

### 问题 1：如何向现有模拟添加另一个空间域？
<a name="working-with_configuring-simulation_domains_spatial_faq_q1"></a>
+ **对于正在运行的模拟** – 无法更改正在运行的模拟的配置。更改架构中的域配置，上传架构和应用程序 zip，然后启动新的模拟。
+ **对于新的模拟** – 将域配置添加到架构，上传架构和应用程序 zip，然后启动新的模拟。

## 空间域问题排查
<a name="working-with_configuring-simulation_domains_spatial_troubleshooting"></a>

当您尝试启动模拟但域配置无效时，可能出现以下错误。

```
"StartError": "[{\"errorType\":\"SchemaFormatInvalid\",\"errorMessage\":
    \"We were unable to determine an arrangement of your domains that would fit 
    within the provided set of workers. This can generally be resolved by 
    increasing the number of workers if able, decreasing your domains\u0027 
    [\u0027\u0027grid_partition\u0027\u0027] values, or adjusting the 
    dimensions of your [\u0027\u0027grid_placement_groups\u0027\u0027].\"}]"
```

**潜在原因**
+ 该架构为应用程序分配的计算资源单位数量多于工作线程上可用的计算资源单位数量。
+ SimSpace Weaver 无法确定将域名放在工作人员身上的安排。当您指定多个空间域，但域网格之间没有公因数或倍数（例如 2x4 格网和 3x5 网格之间）时，就会发生这种情况。

# 网络端点
<a name="working-with_configuring-simulation_domains_endpoints"></a>

自定义应用程序和服务应用程序可具有外部客户端可连接的网络端点。您可以在 `endpoint_config` 中将端口号列表指定为 `ingress_ports` 的值。这些端口号都是 TCP 和 UDP 端口。自定义应用程序或服务应用程序应绑定到您在中指定的端口号`ingress_ports`。 SimSpace Weaver 在运行时动态分配端口号，并将这些端口映射到动态端口。您可以在应用程序开始查找动态（实际）端口号后调用 **describe-app** API。有关更多信息，请参阅快速入门教程中的[获取定制化应用程序的 IP 地址和端口号获取 IP 地址和端口号](working-with_get-ip.md)。

```
domains:
  MyViewDomain:
    launch_apps_via_start_app_call: {}
    app_config:
      package: "s3://weaver-myproject-111122223333-us-west-2/MyViewApp.zip"
      launch_command: ["MyViewApp"]
      required_resource_units:
        compute: 1
      endpoint_config:
        ingress_ports:
          - 7000
```

**注意**  
SimSpace Weaver 应用程序 SDK 版本 1.12.x 项目对应用程序.zip 文件和架构使用单独的存储桶：  
weaver-*lowercase-project-name*--ap *account-number* p-zips-*region*
weaver-*lowercase-project-name*-*account-number*-schemas-*region*

**注意**  
`endpoint_config` 是自定义应用程序和服务应用程序的可选属性。如果您未指定 `endpoint_config`，则应用程序将没有网络端点。

# 配置服务域
<a name="working-with_configuring-simulation_domains_service-domains"></a>

域配置`launch_apps_per_worker:`中存在表示它是一个包含服务应用程序的服务域。 SimSpace Weaver 为您启动和停止服务应用程序。 SimSpace Weaver 启动和停止应用程序时，该应用程序被视为具有托*管生命周期*。 SimSpace Weaver 目前支持在每个工作人员上启动 1 或 2 个服务应用程序。

**Example 配置为在每个工作线程上启动 1 个服务应用程序的域的示例**  

```
domains:
  MyServiceDomain:
    launch_apps_per_worker:
      count: 1
    app_config:
      package: "s3://weaver-myproject-111122223333-app-zips-us-west-2/PlayerConnectionServiceApp.zip"
      launch_command: ["PlayerConnectionServiceApp"]
      required_resource_units:
        compute: 1
      endpoint_config:
        ingress_ports:
          - 9000
          - 9001
```

**Example 配置为在每个工作线程上启动 2 个服务应用程序的域的示例**  

```
domains:
  MyServiceDomain:
    launch_apps_per_worker:
      count: 2
    app_config:
      package: "s3://weaver-myproject-111122223333-app-zips-us-west-2/PlayerConnectionServiceApp.zip"
      launch_command: ["PlayerConnectionServiceApp"]
      required_resource_units:
        compute: 1
      endpoint_config:
        ingress_ports:
          - 9000
          - 9001
```

# 模拟的最长持续时间
<a name="working-with_max-duration"></a>

中的每个模拟都 AWS SimSpace Weaver 有一个*最大持续时间*设置，用于指定模拟可以运行的最大时间。启动模拟时，您可以将最长持续时间作为参数值提供。[`StartSimulation` 应用程序编程接口 (API)](https://docs.aws.amazon.com/simspaceweaver/latest/APIReference/API_StartSimulation.html) 有一个可选参数 `MaximumDuration`。该参数的值是分钟数（m 或 M）、小时数（h 或 H）或天数（d 或 D）。例如，`1h` 或 `1H` 表示 1 小时。当达到此限制时， SimSpace Weaver 将停止模拟。

## 最大值
<a name="working-with_max-duration_max-value"></a>

`MaximumDuration` 的最大有效值为 `14D`，或以小时 (`336H`) 或分钟 (`20160M`) 为单位的等效值。

## 默认值
<a name="working-with_max-duration_default-value"></a>

`MaximumDuration` 参数是可选的。如果您不提供值，则 SimSpace Weaver 使用值`14D`。

## 最小值
<a name="working-wtih_max-duration_min-value"></a>

`MaximumDuration` 的最小有效值是在数值上等效于 `0` 的值。例如，值 `0M`、`0H` 和 `0D` 在数值上都等效于 `0`。

如果您提供最长持续时间的最小值，则模拟在达到`STOPPING`状态后会立即过渡到该`STARTED`状态。

## 使用控制台启动模拟
<a name="working-with_max-duration_console"></a>

当您在 [SimSpace Weaver 控制台](https://console.aws.amazon.com/simspaceweaver)中启动模拟时，可以为**最长持续时间**提供一个值。选择**启动模拟**时，在**模拟设置**表单的**最长持续时间**字段中输入该值。

**重要**  
如果您没有为**最长持续时间**提供值，则 SimSpace Weaver 会使用[默认值](#working-with_max-duration_default-value) (`14D`)。

## 模拟达到最长持续时间时的状态
<a name="working-with_max-duration_sim-state"></a>

当 SimSpace Weaver 自动停止达到最大持续时间的模拟时，模拟的**状态为**`STOPPING`（如果正在进行中）或`STOPPED`。在 [SimSpace Weaver 控制台](https://console.aws.amazon.com/simspaceweaver)中，模拟的**目标状态**仍然是 `STARTED`，因为这是用户请求的最后一个状态。

# 开发应用程序
<a name="working-with_developing-apps"></a>

SimSpace Weaver 开发需要 Amazon Linux 2 (AL2) 用于构建应用程序的环境，因为您的模拟运行在上面 Amazon Linux 在 AWS Cloud。 如果你正在使用 Windows，您可以使用 SimSpace Weaver 应用程序 SDK 中的脚本来创建和启动 Docker 运行的容器 AL2 以及构建 SimSpace Weaver 应用程序所需的依赖关系。你也可以启动 AL2 环境使用 Windows Subsystem for Linux (WSL)，或者使用本机 AL2 系统。有关更多信息，请参阅 [设置您的本地环境用于 SimSpace Weaver](setting-up_local.md)。

**注意**  
无论您如何配置本地开发环境，您的应用程序都可以在中运行 Docker 当您上传容器以在中运行时 AWS 云。**您的应用程序对主机操作系统没有直接访问权限**。

**SimSpace Weaver 应用程序的一般流程**

1. 创建应用程序。

1. 循环：

   1. 通过创建 `Transaction` 开始更新。

      1. 如果模拟关闭，则退出循环。

   1. 处理订阅和所有权实体事件。

   1. 更新模拟。

   1. 提交 `Transaction` 以结束更新。

1. 销毁应用程序。

## 空间应用程序
<a name="working-with_developing-apps_spatial-apps"></a>

每个空间应用程序都有一个所有权区域，即模拟环境下的空间区域。位于空间应用程序所有权区域中的实体存储在应用程序的已分配分区中。对于已分配分区中的所有实体，该单一空间应用程序拥有完全所有权（读取和写入权限）。其他应用程序均无法对这些实体执行写入操作。空间应用程序可推进实体的状态。每个空间应用程序仅拥有 1 个分区。 SimSpace Weaver 使用实体的空间位置编制索引并将其分配给空间应用程序分区。

 SimSpace Weaver 应用程序 SDK 提供了一个示例应用程序。您可以在以下文件夹中找到示例应用程序的空间应用程序的源代码（使用适用于您的操作系统的正确路径分隔符）：

```
sdk-folder\Samples\PathfindingSample\src\SpatialApp
```

## 自定义应用程序
<a name="working-with_developing-apps_custom-apps"></a>

您可以创建并使用自定义应用程序来与模拟进行交互。

**自定义应用程序可以**
+ 创建实体
+ 订阅其他分区
+ 提交更改

**自定义应用程序的一般流程**

1. 创建应用程序。

1. 订阅模拟中的特定区域：

   1. 创建 `Transaction` 以开始首次更新。

   1. 为特定区域创建订阅。

   1. 提交 `Transaction` 以结束更新。

1. 循环：

   1. 创建 `Transaction` 以开始更新。

      1. 如果模拟关闭，则退出循环。

   1. 处理状态更改。

   1. 提交 `Transaction` 以结束更新。

1. 销毁应用程序。

定制化 App 创建实体后，必须将实体转移到空间域，实体才能在模拟中以空间形式存在。 SimSpace Weaver 使用实体的空间位置将实体放置在相应的空间应用程序分区中。将实体转移到空间域中后，创建实体的自定义应用程序无法更新或删除实体。

 SimSpace Weaver 应用程序 SDK 提供了一个示例应用程序。您可以将示例应用程序中包含的自定义应用程序用作自己的自定义应用程序的模型。您可以在以下文件夹中找到示例应用程序的 view 应用程序（自定义应用程序）的源代码（使用适用于您的操作系统的正确路径分隔符）：

```
sdk-folder\Samples\PathfindingSample\src\ViewApp
```

# 开发客户端应用程序
<a name="working-with_developing-client-applications"></a>

您可能想要将客户端连接到模拟的一些原因包括：
+ 将实时交通信息注入城市级模拟中。
+ 创建*human-in-the-loop*模拟，其中人工操作员控制仿真的某些方面。
+ 使用户可以与模拟进行交互（例如，训练模拟）。

这些示例中的自定义应用程序充当模拟状态与外部环境之间的接口。客户端连接到自定义应用程序，以便与模拟进行交互。

SimSpace Weaver 不处理客户端应用程序及其与您的自定义应用程序的通信。您负责客户端应用程序的设计、创建、操作和安全，以及它们与您的自定义应用程序之间的通信。 SimSpace Weaver 仅公开每个自定义应用程序的 IP 地址和端口号，以便客户端可以连接到它们。

 SimSpace Weaver 应用程序 SDK 为其示例应用程序提供客户端。您可以将这些客户端用作自己的客户端应用程序的模型。您可以在以下文件夹中找到示例应用程序客户端的源代码：

------
#### [ Docker ]

```
sdk-folder\packaging-tools\clients\PathfindingSampleClients
```

------
#### [ WSL ]

**重要**  
为方便起见，我们提供了这些说明。它们可用于 Windows Subsystem for Linux (WSL)，并且不受支持。有关更多信息，请参阅 [设置您的本地环境用于 SimSpace Weaver](setting-up_local.md)。

```
sdk-folder/packaging-tools/clients/PathfindingSampleClients
```

------

有关构建和使用示例应用程序客户端的更多信息，请参阅中的教程[入门 SimSpace Weaver](getting-started.md)。

# 获取定制化应用程序的 IP 地址和端口号
<a name="working-with_get-ip"></a>

要查看您的模拟，您需要创建一个定制化 App 并通过客户端连接到该应用程序。有关更多信息，请参阅中的教程[入门 SimSpace Weaver](getting-started.md)。您可以使用以下步骤获取定制化应用程序的 IP 地址和端口号。使用适合您的操作系统的路径分隔符（例如，`\`在 Windows 和 Linux `/` 中）。

**获取 IP 地址和端口号**

1. 使用 ** ListSimulations**API 获取模拟的名称。

   ```
   aws simspaceweaver list-simulations
   ```

   输出示例：

   ```
   {
       "Simulations": [
           {
               "Status": "STARTED",
               "CreationTime": 1664921418.09,
               "Name": "MyProjectSimulation_22-10-04_22_10_15",
               "Arn": "arn:aws:simspaceweaver:us-west-2: 111122223333:simulation/MyProjectSimulation_22-10-04_22_10_15",
               "TargetStatus": "STARTED"
           }
       ]
   
   }
   ```

1. 使用 ** DescribeSimulation**API 获取模拟中的域名列表。

   ```
   aws simspaceweaver describe-simulation --simulation simulation-name
   ```

   在输出的 `LiveSimulationState` 部分中查找 `Domains` 部分。

   输出示例：

   ```
       "LiveSimulationState": {
           "Domains": [
               {
                   "Type": "",
                   "Name": "MySpatialSimulation",
                   "Lifecycle": "Unknown"
               },
               {
                   "Type": "",
                   "Name": "MyViewDomain",
                   "Lifecycle": "ByRequest"
               }
           ],
   ```

1. 使用 ** ListApps**API 获取网域中的自定义应用程序列表。例如，示例项目中视图（自定义）应用程序的域名为`MyViewDomain`。在输出中查找应用程序名称。

   ```
   aws simspaceweaver list-apps --simulation simulation-name --domain domain-name
   ```

   输出示例：

   ```
    
   {
       "Apps": [
           {
               "Status": "STARTED",
               "Domain": "MyViewDomain",
               "TargetStatus": "STARTED",
               "Name": "ViewApp",
               "Simulation": "MyProjectSimulation_22-10-04_22_10_15"
           }
       ]
   }
   ```

1. 使用 ** DescribeApp**API 获取 IP 地址和端口号。对于示例项目，域名称为 `MyViewDomain`，应用程序名称为 `ViewApp`。

   ```
   aws simspaceweaver describe-app --simulation simulation-name --domain domain-name --app app-name
   ```

   IP 地址和端口号位于输出的 `EndpointInfo` 块中。IP 地址是 `Address` 的值，端口号是 `Actual` 的值。

   输出示例：

   ```
   {
       "Status": "STARTED",
       "Domain": "MyViewDomain",
       "TargetStatus": "STARTED",
       "Simulation": "MyProjectSimulation_22-10-04_22_10_15",
       "LaunchOverrides": {
           "LaunchCommands": []
       },
       "EndpointInfo": {
           "IngressPortMappings": [
               {
                   "Declared": 7000,
                   "Actual": 4321
               }
           ],
           "Address": "198.51.100.135"
       },
       "Name": "ViewApp"
   }
   ```
**注意**  
`Declared` 的值是应用程序代码应绑定的端口号。的值`Actual`是向客户端 SimSpace Weaver 公开的用于连接您的应用程序的端口号。 SimSpace Weaver 将`Declared`端口映射到端`Actual`口。

# 启动虚幻引擎视图客户端
<a name="working-with_unreal-client"></a>

 导航至：

```
sdk-folder/Samples/PathfindingSample/tools/cloud
```

1. 运行以下命令之一：
   + 码头工人：`python quick-start.py`
   + WSL：`python quick-start.py --al2`

1. 获取 IP 地址和 “实际” 端口号。它们将出现在运行 quick-start.py 的控制台输出中，或者按照中的步骤获取它们[获取定制化应用程序的 IP 地址和端口号获取 IP 地址和端口号](working-with_get-ip.md)。

1.  导航至：

   ```
   sdk-folder/Clients/TCP/UnrealClient/lib
   ```

1.  运行以下命令来构建 NNG 库：

   ```
   cmake -S . -B build 
   cmake --build build --config RelWithDebInfo 
   cmake --install build
   ```

1.  在**文本编辑器**中，打开 `view_app_url.txt`。

1.  在查看应用程序中更新 URL 的 IP 地址和端口号：`tcp://ip-address:actual-port-number`（它应该类似于 `tcp://198.51.100.135:1234`）。

1.  在**虚幻编辑器**中，选择**播放**。

## 故障排除
<a name="working-with_unreal-client_troubleshooting"></a>
+  **NNG CMake 安装步骤失败，并显示 “可能需要管理权限”：**

  ```
  CMake Error at build/_deps/nng-build/src/cmake_install.cmake:39 (file):
    file cannot create directory: C:/Program Files
    (x86)/ThirdPartyNngBuild/lib.  Maybe need administrative privileges.
  Call Stack (most recent call first):
    build/_deps/nng-build/cmake_install.cmake:37 (include)
    build/cmake_install.cmake:73 (include)
  ```
  +  **解决方案：**如果`nng.lib`或`nng.so`存在于 UnrealClient /lib 目录中，则可以放心地忽略此错误。如果没有，请尝试在具有管理员权限的终端中运行 cmake build 命令。
+  **“CMake 查找 nng 提供的软件包配置文件”：**

  ```
  CMake Error at CMakeLists.txt:23 (find_package):
  By not providing "Findnng.cmake" in CMAKE_MODULE_PATH this project has
   asked CMake to find a package configuration file provided by "nng", but
   CMake did not find one.
  ```
  +  **解决方法：** CMake 在查找`Findnng.cmake`文件时遇到问题。使用构建时 CMake，请添加参数`-DTHIRD_PARTY_LIB_PATH sdk-folder/ThirdParty`。在重新运行 CMake 构建之前，请确保`Findnng.cmake`文件仍在`ThirdParty`目录中。

    ```
    cmake -S . -B build -DTHIRD_PARTY_LIB_PATH sdk-folder/ThirdParty
    cmake --build build --config RelWithDebInfo 
    cmake --install build
    ```

# 当地发展 SimSpace Weaver
<a name="working-with_local-development"></a>

您可以在本地部署 SimSpace Weaver 应用程序，以便进行快速测试和调试。

**要求**
+ 完成[正在设置 SimSpace Weaver](setting-up.md)中的步骤。

**Topics**
+ [步骤 1：启动本地模拟](working-with_local_launch.md)
+ [第 2 步：查看您的本地模拟](working-with_local-development_view.md)
+ [步骤 3：停止本地模拟（在 Windows 上是可选的）](working-with_local-development_stop-sim.md)
+ [对本地开发进行故障排除 SimSpace Weaver](working-with_local-development_troubleshooting.md)

# 步骤 1：启动本地模拟
<a name="working-with_local_launch"></a>

1. 导航到

   ```
   cd sdk-folder/Samples/sample-name/tools/local
   ```

1. 运行以下命令在本地构建和启动模拟。

   ```
   python quick-start.py
   ```

   该脚本将执行以下操作：

   1. 构建 项目。
      +  `quick-start.py`调用 build.py 中定义的`build_project`函数。此步骤将因项目而异。对 CMake 于 PathfindingSample，使用。 CMake 和 Docker 命令可以在 build.py 中找到。

   1. 启动本地模拟
      + 该脚本将为架构中定义的每个空间分区启动一个本地进程。
      + 该脚本将为架构中定义的每个自定义应用程序启动一个进程。
      + 空间应用程序将首先启动，然后是自定义应用程序，每个应用程序都按照它们在架构中的显示顺序启动。

**重要**  
在不支持 GUI 的环境（例如控制台 SSH 会话）中启动时，请使用`--noappwindow`选项将所有输出重定向到当前终端。

**重要**  
对于 Linux 用户，该脚本假设您的系统具有该`xterm`命令。如果您的 Linux 发行版没有该`xterm`命令，请使用该`--noappwindow`选项将所有输出重定向到当前终端。
+  -h、--help 
  +  列出这些参数。
+  --干净 
  +  在构建之前，请删除构建目录的内容。
+  --nobuild 
  +  跳过重建项目。
+  --noappwindow 
  +  不要为每个应用程序打开一个新窗口。而是将 stdout 重定向到当前终端。
+  --日志文件 
  +  将控制台输出写入日志文件。
+  --控制台客户端 
  +  自动连接配置中列出的控制台客户端。
+  --架构架构
  + 此调用将使用什么架构。config.py 中默认为 “架构”。

# 第 2 步：查看您的本地模拟
<a name="working-with_local-development_view"></a>

要查看您的本地模拟，您可以使用随附的任何客户端 SimSpaceWeaverAppSdkDistributable。有关构建和使用示例客户端的更多信息，请参阅中的教程[入门 SimSpace Weaver](getting-started.md)。

您必须更新客户端的 IP 地址和端口号，才能连接到查看应用程序以进行本地模拟。请务必使用以下值和 SimSpace Weaver Local:

```
tcp://127.0.0.1:7000
```

根据选择的客户端，您可以按如下方式更新 IP 地址和端口号：
+ **Unreal** – 更改 `view_app_url.txt` 第 1 行中的 URL
+ **控制台** – 使用 IP 地址和端口号 URL 作为参数，启动客户端

# 步骤 3：停止本地模拟（在 Windows 上是可选的）
<a name="working-with_local-development_stop-sim"></a>

**注意**  
此步骤在 Linux 上是必需的，但在 Windows 上是可选的。

1.  导航至：

   ```
   sdk-folder/Samples/sample-name/tools/local
   ```

1.  运行以下命令以停止本地模拟并删除所有共享内存资源。

   ```
   python stop-and-delete.py
   ```

    该脚本将执行以下操作：
   +  停止本地进程。
   +  删除共享内存对象（仅在 Linux 上需要）。

**stop-and-delete.py 参数**
+  -h、--help 
  +  列出这些参数。
+  --停下来 
  +  仅尝试停止进程。
+  --删除 
  +  仅尝试删除共享内存资源。
+  --进程 
  +  要停止的进程名称。如果您的进程名称与架构中的软件包名称不匹配，请使用此选项。
+  --架构架构 
  +  此调用将使用什么架构。config.py 中默认为 “架构” 的值。

# 对本地开发进行故障排除 SimSpace Weaver
<a name="working-with_local-development_troubleshooting"></a>
+  **Linux：找不到 xterm 命令/无法打开** 
  + 本地脚本假设在 Linux 上运行时存在 xterm 命令。如果您没有 xterm 命令或者正在不支持 GUI 的环境中运行，请在运行快速启动脚本时使用该`--noappwindow`选项。
+  **没有打开任何应用程序窗口！**
  +  当本地模拟立即崩溃时，就会发生这种情况。要在崩溃后查看控制台输出，请在运行快速启动脚本时使用`--noappwindow`或`--logfile`选项。
+  **在视图应用程序启动或视图客户端连接后，模拟没有启动！**
  +  使用该`—noappwindow`选项运行通常可以解决这类问题。否则，重启几次也会成功（尽管速度要低得多）。

# AWS SimSpace Weaver 应用程序 SDK
<a name="working-with_app-sdk"></a>

A SimSpace Weaver pp SDK 提供了 APIs 可用于控制模拟中的实体和响应 SimSpace Weaver 事件的功能。其中包括以下命名空间：
+ **API** – API 的核心定义及其用途

链接到以下库：
+ `libweaver_app_sdk_cxx_v1_full.so`

**重要**  
当您在 AWS 云中运行应用程序时，该库可用于动态链接。您无需将其与应用程序一起上传。

**注意**  
 SimSpace Weaver 应用程序 SDK APIs 控制仿真中的数据。 APIs它们与 SimSpace Weaver 服务是分开的 APIs，后者控制您的 SimSpace Weaver 服务资源（例如模拟、应用程序和时钟）。 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``Aws::WeaverRuntime::ErrorCode`包含表示错误代码的 Rust App SDK.

**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;
        }
    }
}
```

**重要**  
只能在 `Result<void> DestroyApplication(Application&& app)` 之后调用 `Api::Commit()`。在更新过程中销毁应用程序可能会导致未定义的行为。

**重要**  
您必须在程序退出之前调用 `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>

当实体进入应用程序`Api:Entity`的订阅区域时`CreateEntity()`，您可以使用从或从所有权变更事件`Result<Api::Entity>`返回的 and（有关更多信息，请参阅[实体事件](working-with_app-sdk_events.md)）。`Store` `Load` APIs 我们建议您跟踪您的`Api::Entity`对象，以便可以将它们与这些对象一起使用 APIs。

**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-511 是保留值。你的实体 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`。在 `Commit(Transaction&&)` 过程中，所有权会转移到新的 `Domain`。参数

`txn`  
当前 `Transaction`。

`entity`  
更改 `Entity` 的目标 `Domain`。

`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>

以下示例演示如何存储（写入 State Fabric）应用程序拥有的实体的字段数据。这些示例使用以下函数：

```
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 使用 a 的示例 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>

以下示例演示如何加载（从 State Fabric 读取）实体的字段数据。这些示例使用以下函数：

```
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 使用 a 的示例 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>

对于已经从应用程序所有权和订阅区域中移除的实体，您无法加载（从 State Fabric 读取）实体字段数据。以下示例会导致错误，因为 `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>

您可以使用整数数据结构存储（写入 State Fabric）实体的位置。这些示例使用以下函数：

```
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 使用 a 的示例 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>

您可以使用整数数据结构加载（从 State Fabric 读取）实体的位置。这些示例使用以下函数：

**注意**  
您必须将 `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 使用 a 的示例 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 来处理这些事件：
+ `Api::SubscriptionChangeList`
+ `Api::OwnershipEvents`

然后，您可以将更改与之前存储的数据进行比较。

以下示例说明了如何处理实体所有权更改事件。此示例假设，对于在已订阅实体和拥有的实体（任一方向）之间过渡的实体，所有权remove/add event occurs first followed by the subscription remove/add事件将在下一个勾选中显示。

**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` 库。您可以使用以下模式来检查 API 调用返回的 `Result` 和捕获错误。

```
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()` 函数的重写。此版本使用`WEAVERRUNTIME_TRY`宏而不是 if-else 控制结构。请注意，此函数的返回类型为 `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>`，您可以将错误代码从 `BeginUpdate()` 提供给调用 `Update()` 的函数。您可以重复此过程，继续将错误代码发送到调用堆栈以下。

# 泛型和域类型
<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 示框架提供了最小版本的 AzCore 数学库，其中包含`Vector3`和`Aabb`。有关更多信息，请参阅以下路径下的头文件：
+ `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);

    ...
}
```

# AWS SimSpace Weaver 演示框架
<a name="working-with_demo-framework"></a>

 AWS SimSpace Weaver 演示框架（演示框架）是一个可用于开发 SimSpace Weaver 应用程序的实用程序库。

**该演示框架提供以下内容**
+ 供您使用和检查的代码示例及编程模式
+ 抽象和实用程序函数，可简化简单应用程序的开发
+ 测试 SimSpace Weaver 应用程序 SDK 实验功能的更简单方法

我们设计了具有低级访问权限的 SimSpace Weaver 应用程序 SDK， SimSpace Weaver APIs 以提供更高的性能。相比之下，我们设计的演示框架旨在提供更高级别的抽象和访问权限 APIs ，使其 SimSpace Weaver 更易于使用。与直接使用 SimSpace Weaver 应用程序 SDK 相比，易用性的代价是性能水平较低。可容忍较低性能的模拟（例如，不要求实时性能的模拟）可能适合使用演示框架。对于复杂的应用程序，我们建议您使用 SimSpace Weaver 应用程序 SDK 中的本机功能，因为演示框架不是完整工具包。

**该演示框架包括以下内容**
+ 支持和演示以下功能的工作代码示例：
  + 应用程序流量管理
  + 回调驱动的实体事件处理
+ 一组第三方实用程序库：
  + spdlog （一个日志库）
  + 最小版本的 AZCore （数学库）仅包含：
    + Vector3
    + Aabb
  + cxxopts （命令行选项解析器库）
+ 特定于的实用函数 SimSpace Weaver

演示框架由一个库、源文件和 CMakeLists。 这些文件包含在 SimSpace Weaver 应用程序 SDK 可分发包中。

# 使用服务限额
<a name="working-with_quotas"></a>

本节介绍如何使用的服务配额 SimSpace Weaver。**限额**也称为**限制**。有关服务限额的列表，请参阅 [SimSpace Weaver 端点和配额](service-quotas.md)。本节 APIs中的来自**应用程序**集 APIs。应用程序 APIs与服务不同 APIs。该应用程序 APIs 是 SimSpace Weaver 应用程序 SDK 的一部分。你可以在本地系统上的 app SDK 文件夹 APIs 中找到该应用程序的文档：

```
sdk-folder\SimSpaceWeaverAppSdk-sdk-version\documentation\index.html
```

**Topics**
+ [获取应用程序的限制](#working-with_quotas_get-app-limits)
+ [获取应用程序使用的资源数量](#working-with_quotas_get-app-resources)
+ [重置指标](#working-with_quotas_reset-metrics)
+ [超出限制](#working-with_quotas_exceed-limit)
+ [内存不足](#working-with_quotas_out-of-memory)
+ [最佳实践](#working-with_quotas_best-practices)

## 获取应用程序的限制
<a name="working-with_quotas_get-app-limits"></a>

您可以使用 **RuntimeLimits** 应用程序 API 来查询应用程序的限制。

```
Result<Limit> RuntimeLimit(Application& app, LimitType type)
```参数

**Application& 应用程序**  
对应用程序的引用。

**LimitType 类型**  
具有以下限制类型的枚举：  

```
enum LimitType {
    Unset = 0,
    EntitiesPerPartition = 1,
    RemoteEntityTransfers = 2,
    LocalEntityTransfers = 3
};
```

以下示例查询实体计数限制。

```
WEAVERRUNTIME_TRY(auto entity_limit,
    Api::RuntimeLimit(m_app, Api::LimitType::EntitiesPerPartition))
Log::Info("Entity count limit", entity_limit.value);
```

## 获取应用程序使用的资源数量
<a name="working-with_quotas_get-app-resources"></a>

您可以调用 **RuntimeMetrics** 应用程序 API 来获取应用程序使用的资源数量：

```
Result<std::reference_wrapper<const AppRuntimeMetrics>> RuntimeMetrics(Application& app) noexcept
```参数

**Application& 应用程序**  
对应用程序的引用。

API 会返回对包含指标的 struct 的引用。计数器指标包含一个运行总值，并且只会增加。量规指标包含一个可以增加或减少的值。每当有事件增加值时，应用程序运行时都会更新计数器。只有在您调用 API 时，运行时才会更新量规。 SimSpace Weaver 保证该引用在应用程序的生命周期内有效。重复调用 API 不会更改引用。

```
struct AppRuntimeMetrics {
    uint64_t total_committed_ticks_gauge,

    uint32_t active_entity_gauge,
    uint32_t ticks_since_reset_counter,

    uint32_t load_field_counter,
    uint32_t store_field_counter,

    uint32_t created_entity_counter,
    uint32_t deleted_entity_counter,

    uint32_t entered_entity_counter,
    uint32_t exited_entity_counter,

    uint32_t rejected_incoming_transfer_counter,
    uint32_t rejected_outgoing_transfer_counter
}
```

## 重置指标
<a name="working-with_quotas_reset-metrics"></a>

**ResetRuntimeMetrics** 应用程序 API 会重置中的 `AppRuntimeMetrics` struct 中值。

```
Result<void> ResetRuntimeMetrics(Application& app) noexcept
```

以下示例演示如何在应用程序中调用 **ResetRuntimeMetrics**。

```
if (ticks_since_last_report > 100)
{
    auto metrics = WEAVERRUNTIME_EXPECT(Api::RuntimeMetrics(m_app));
    Log::Info(metrics);

    ticks_since_last_report = 0;

    WEAVERRUNTIME_EXPECT(Api::ResetRuntimeMetrics(m_app));
}
```

## 超出限制
<a name="working-with_quotas_exceed-limit"></a>

超过限制的应用程序 API 调用将返回 `ErrorCode::CapacityExceeded`，但实体转移除外。作为**提交**和 **BeginUpdate** 应用程序 API 操作的一部分， SimSpace Weaver 会异步处理实体转移，因此，如果由于实体转移限制而导致转移失败，则不存在会返回错误的特定操作。要检测转移失败，可以将 `rejected_incoming_transfer_counter` 和 `rejected_outgoing_transfer_counter`（在 `AppRuntimeMetrics` struct 中）的当前值与其先前值进行比较。被拒绝的实体不会出现在分区中，但应用程序仍然可以模拟它们。

## 内存不足
<a name="working-with_quotas_out-of-memory"></a>

SimSpace Weaver 使用垃圾收集器进程来清理和释放释放的内存。写入数据的速度可能超过垃圾收集器释放内存的速度。如果发生这种情况，写入操作可能会超过应用程序的预留内存限制。 SimSpace Weaver 将返回内部错误，其中包含一条具有 `OutOfMemory`（以及其他详细信息）的消息。有关更多信息，请参阅 [跨时间分散写入](#working-with_quotas_best-practices_spread-writes)。

## 最佳实践
<a name="working-with_quotas_best-practices"></a>

以下最佳实践是设计应用程序以避免超出限制的一般指南。这些实践可能不适用于您的特定应用程序设计。

### 经常监视并减慢速度
<a name="working-with_quotas_best-practices_monitor"></a>

您应该经常监控自己的指标，并减慢接近限制的操作。

### 避免超过订阅限制和转移限制
<a name="working-with_quotas_best-practices_subscription-and-xfer"></a>

如果可能，设计模拟以减少远程订阅和实体转移的数量。您可以使用置放群组，以便在同一个工作线程上放置多个分区，从而减少在工作线程之间进行远程实体转移的需求。

### 跨时间分散写入
<a name="working-with_quotas_best-practices_spread-writes"></a>

在一个时间周期中更新的数量和大小可能会对提交事务所需的时间和内存产生重大影响。内存需求过大可能会导致应用程序运行时内存不足。您可以跨时间分散写入次数，以降低每个时间周期内更新的平均总大小。这可以帮助提高性能并避免超出限制。我们建议您在每个时间周期内的平均写入数量不要超过 12 MB，或者每个实体的平均写入数量不要超过 1.5 KB。

# 调试模拟
<a name="working-with_debugging"></a>

您可以使用以下方法获取模拟的相关信息。

**主题**
+ [使用 SimSpace Weaver Local 然后看看控制台的输出](#working-with_debugging_use-local)
+ [在 Amazon 日志中查看你的 CloudWatch 日志](#working-with_debugging_logs)
+ [使用 **描述** API 调用](#working-with_debugging_api)
+ [连接客户端](#working-with_debugging_client)

## 使用 SimSpace Weaver Local 然后看看控制台的输出
<a name="working-with_debugging_use-local"></a>

我们建议您先在本地开发模拟，然后在 AWS 云中运行模拟。当你使用运行时，你可以直接查看控制台输出 SimSpace Weaver Local。 有关更多信息，请参阅[当地发展 SimSpace Weaver](working-with_local-development.md)。

## 在 Amazon 日志中查看你的 CloudWatch 日志
<a name="working-with_debugging_logs"></a>

当您在控制台中运行模拟时，应用程序的输出 AWS 云 将发送到 Amazon Logs 中的 CloudWatch 日志流。您的模拟还会写入其他日志数据。如果您想让模拟写入日志数据，则必须在模拟架构中启用日志记录。有关更多信息，请参阅 [SimSpace Weaver 在 Amazon CloudWatch 日志中登录](cloudwatch-logs.md)。

**警告**  
您的模拟可能生成大量日志数据。日志数据可能增长得非常快。您应该密切关注日志，并在不再需要运行模拟时停止模拟。日志可能会产生高昂的成本。

## 使用 **描述** API 调用
<a name="working-with_debugging_api"></a>

您可以使用以下服务在中 APIs 获取有关您的模拟的信息。 AWS 云
+ **ListSimulations**— 在中获取所有模拟的 AWS 云清单。  
**Example 示例**  

  ```
  aws simspaceweaver list-simulations
  ```
+ **DescribeSimulation** – 获取有关模拟的详细信息。  
**Example 示例**  

  ```
  aws simspaceweaver describe-simulation --simulation MySimulation
  ```
+ **DescribeApp** – 获取有关应用程序的详细信息。  
**Example 示例**  

  ```
  aws simspaceweaver describe-app --simulation MySimulation --domain MyCustomDomain --app MyCustomApp
  ```

有关更多信息 SimSpace Weaver APIs，请参阅[SimSpace Weaver API 参考资料](api-reference.md)。

## 连接客户端
<a name="working-with_debugging_client"></a>

您可以将客户端连接到在模拟架构中使用 `endpoint_config` 定义的正在运行的自定义应用程序或服务应用程序。 SimSpace Weaver 应用程序 SDK 包含可用于查看示例应用程序的示例客户端。您可以查看这些示例客户端的源代码和示例应用程序，以便了解如何创建自己的客户端。有关如何构建和运行示例客户端的更多信息，请参阅中的教程[入门 SimSpace Weaver](getting-started.md)。

您可以在以下文件夹中找到示例客户端的源代码：
+ `sdk-folder\packaging-tools\clients\PathfindingSampleClients\`

# 调试本地模拟
<a name="working-with_debugging_local"></a>

您可以使用 Microsoft Visual Studio 调试 SimSpace Weaver Local 应用程序。有关如何使用 Visual Studio 进行调试的更多信息，请参阅 [Microsoft Visual Studio documentation。](https://learn.microsoft.com/en-us/visualstudio/debugger/debugger-feature-tour)。

**调试本地模拟**

1. 请确保您的 `schema.yaml` 位于工作目录中。

1. 在 **Visual Studio** 中，打开要调试的每个应用程序的上下文菜单（如 `PathfindingSampleLocalSpatial` 或 `PathfindingSampleLocalView`），然后在调试部分设置工作目录。

1. 打开要调试的应用程序的上下文菜单，然后选择**设置为启动项目**。

1. 选择 F5 以开始调试应用程序。

调试模拟的要求与正常运行模拟的要求相同。您必须启动在架构中指定的空间应用程序数量。例如，如果您的架构指定一个 2x2 网格，而您在调试模式下启动一个空间应用程序，则只有在您再启动 3 个空间应用程序后，模拟才会运行（无论是否在调试模式下）。

要调试自定义应用程序，您必须先启动空间应用程序，然后在调试程序中启动自定义应用程序。

请注意，您的模拟以锁定步进运行。当应用程序遇到断点时，所有其他应用程序都会暂停。从该断点继续运行后，其他应用程序也会继续运行。

# 自定义容器
<a name="working-with_custom-containers"></a>

AWS SimSpace Weaver 应用程序在容器化 Amazon Linux 2 (AL2) 环境中运行。在中 AWS 云，在根据亚马逊弹性容器注册表 (Amazon ECR) Container Registry 提供的`amazonlinux:2`映像构建的 Docker 容器中 SimSpace Weaver 运行您的模拟。您可以创建自定义 Docker 映像，将其存储在 Amazon ECR 中，然后使用该映像进行模拟，而不是使用我们提供的默认 Docker 映像。

您可以使用自定义容器来管理软件依赖项，并包含标准 Docker 映像中没有的其他软件组件。例如，您可以将应用程序使用的公开可用软件库添加到容器中，并且只将您的自定义代码包含在应用程序 zip 文件中。

**重要**  
我们仅支持托管在 Amazon ECR 存储库中的 AL2 Docker 镜像，无论是在亚马逊 ECR 公共库中还是在您的私有 Amazon ECR 注册表中。我们不支持在 Amazon ECR 外部托管的 Docker 映像。有关 Amazon ECR 的更多信息，请参阅 *[Amazon Elastic Container Registry 文档](https://docs.aws.amazon.com/ecr)*。

**Topics**
+ [创建自定义容器](working-with_custom-containers_create.md)
+ [修改项目以使用自定义容器](working-with_custom-containers_modify-project.md)
+ [有关自定义容器的常见问题解答](working-with_custom-containers_faq.md)
+ [自定义容器故障排除](working-with_custom-containers_troubleshooting.md)

# 创建自定义容器
<a name="working-with_custom-containers_create"></a>

这些说明假设您知道如何使用 Docker 和 Amazon Elastic Container Registry (Amazon ECR)。有关 Amazon ECR 的更多信息，请参阅 *[Amazon ECR 用户指南](https://docs.aws.amazon.com/AmazonECR/latest/userguide)*。

**先决条件**
+ 用于执行这些操作的 IAM 身份（用户或角色）具有使用 Amazon ECR 的正确权限
+ 本地系统上已安装 Docker

**创建自定义容器**

1. 创建 `Dockerfile`。

   `Dockerfile`要运行的 AWS SimSpace Weaver 应用程序，请从 Amazon ECR 中的Amazon Linux 2图像开始。

   ```
   # parent image required to run AWS SimSpace Weaver apps
   FROM public.ecr.aws/amazonlinux/amazonlinux:2
   ```

1. 构建 `Dockerfile`。

1. 将容器映像上传到 Amazon ECR。
   + [使用 AWS 管理控制台。](https://docs.aws.amazon.com/AmazonECR/latest/userguide/getting-started-console.html)
   + [使用 AWS Command Line Interface。](https://docs.aws.amazon.com/AmazonECR/latest/userguide/getting-started-cli.html)
**注意**  
如果您在尝试将容器映像上传到 Amazon ECR 时出现 `AccessDeniedException` 错误，则说明您的 IAM 身份（用户或角色）可能没有使用 Amazon ECR 所需的权限。您可以将`AmazonEC2ContainerRegistryPowerUser` AWS 托管策略附加到您的 IAM 身份，然后重试。有关如何附加策略的更多信息，请参阅《AWS Identity and Access Management 用户指南》中的[添加和删除 IAM 身份权限](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_manage-attach-detach.html)。**

# 修改项目以使用自定义容器
<a name="working-with_custom-containers_modify-project"></a>

这些说明假设您已经知道如何使用， AWS SimSpace Weaver 并希望提高应用程序存储和开发工作流程的 AWS 云 效率。

**先决条件**
+ 在 Amazon Elastic Container Registry (Amazon ECR) 中有一个自定义容器。有关创建自定义容器的更多信息，请参阅[创建自定义容器](working-with_custom-containers_create.md)。

**修改项目以使用自定义容器**

1. 为项目的模拟应用程序角色添加使用 Amazon ECR 的权限。

   1. 如果您目前没有具有以下权限的 IAM 策略，请创建该策略。我们建议使用策略名称 `simspaceweaver-ecr`。有关如何创建 IAM 策略的更多信息，请参阅《AWS Identity and Access Management 用户指南》中的[创建 IAM 策略](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_create.html)。

      ```
      {
          "Version": "2012-10-17",		 	 	 
          "Statement": [
              {
                  "Sid": "Statement",
                  "Effect": "Allow",
                  "Action": [
                      "ecr:BatchGetImage",
                      "ecr:GetDownloadUrlForLayer",
                      "ecr:GetAuthorizationToken"
                  ],
                  "Resource": "*"
              }
          ]
      }
      ```

   1. 查找项目的模拟应用程序角色的名称：

      1. 在文本编辑器中，打开 CloudFormation 模板：

         ```
         sdk-folder\PackagingTools\sample-stack-template.yaml
         ```

      1. 在 `WeaverAppRole` 下找到 `RoleName` 属性。该值是项目的模拟应用程序角色的名称。  
**Example**  

         ```
         AWSTemplateFormatVersion: "2010-09-09"
         Resources:
           WeaverAppRole:
             Type: 'AWS::IAM::Role'
             Properties:
               RoleName: 'weaver-MySimulation-app-role'
               AssumeRolePolicyDocument:
                 Version: "2012-10-17"		 	 	 
                 Statement:
                 - Effect: Allow
                   Principal:
                     Service:
                       - 'simspaceweaver.amazonaws.com'
         ```

   1. 将 `simspaceweaver-ecr` 策略附加到项目的模拟应用程序角色。有关如何附加策略的更多信息，请参阅《AWS Identity and Access Management 用户指南》中的[添加和删除 IAM 身份权限](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_manage-attach-detach.html)。**

   1. 导航到`sdk-folder`并运行以下命令以更新示例 SimSpace Weaver 堆栈：

      ```
      python setup.py --cloudformation
      ```

1. 在项目的模拟架构中指定您的容器映像。
   + 您可以在 `simulation_properties` 下添加可选 `default_image` 属性，为所有域指定默认的自定义容器映像。
   + 在 `app_config` 中为要使用自定义容器映像的域添加 `image` 属性。指定 Amazon ECR 存储库 URI 作为值。您可以为每个域指定不同的映像。
     + 如果没有为域指定 `image`，但已指定 `default_image`，则该域中的应用程序将使用默认映像。
     + 如果`image`未为网域指定且`default_image`未指定，则该网域中的应用程序将在标准 SimSpace Weaver 容器中运行。  
**Example 包含自定义容器设置的架构片段**  

   ```
   sdk_version: "1.17.0"
   simulation_properties:
     log_destination_service: "logs"
     log_destination_resource_name: "MySimulationLogs"
     default_entity_index_key_type: "Vector3<f32>"
     default_image: "111122223333.dkr.ecr.us-west-2.amazonaws.com/my-ecr-repository:latest" # image to use if no image specified for a domain
   domains:
     MyCustomDomain:
       launch_apps_via_start_app_call: {}
       app_config:
         package: "s3://weaver-myproject-111122223333-us-west-2/MyViewApp.zip" 
         launch_command: ["MyViewApp"]  
         required_resource_units:
           compute: 1
         endpoint_config:
           ingress_ports:
             - 7000
         image: "111122223333.dkr.ecr.us-west-2.amazonaws.com/my-ecr-repository:latest" # custom container image to use for this domain 
     MySpatialDomain:
       launch_apps_by_partitioning_strategy:
         partitioning_strategy: "MyGridPartitioning"
         grid_partition:
           x: 2
           y: 2
       app_config:
         package: "s3://weaver-myproject-111122223333-us-west-2/MySpatialApp.zip" 
         launch_command: ["MySpatialApp"] 
         required_resource_units:
           compute: 1
         image: "111122223333.dkr.ecr.us-west-2.amazonaws.com/my-ecr-repository:latest" # custom container image to use for this domain
   ```

1. 照常构建和上传项目。

# 有关自定义容器的常见问题解答
<a name="working-with_custom-containers_faq"></a>

## 问题 1：如果我想更改容器中的内容，该怎么办？
<a name="working-with_custom-containers_faq_q1"></a>
+ **对于正在运行的模拟** – 无法更改正在运行的模拟的容器。您必须构建一个新容器并启动使用该容器的新模拟。
+ **对于新模拟** – 构建一个新容器，将其上传到 Amazon Elastic Container Registry (Amazon ECR)，然后启动使用该容器的新模拟。

## 问题 2：如何更改模拟的容器映像？
<a name="working-with_custom-containers_faq_q2"></a>
+ **对于正在运行的模拟** – 无法更改正在运行的模拟的容器。您必须启动一个使用该容器的新模拟。
+ **对于新的模拟** – 在项目的模拟架构中指定新的容器映像。有关更多信息，请参阅 [修改项目以使用自定义容器](working-with_custom-containers_modify-project.md)。

# 自定义容器故障排除
<a name="working-with_custom-containers_troubleshooting"></a>

**Topics**
+ [AccessDeniedException 将您的图片上传到亚马逊 Elastic Container Registry (Amazon ECR) 时](working-with_custom-containers_troubleshooting_access-denied.md)
+ [使用自定义容器的模拟无法启动](working-with_custom-containers_troubleshooting_no-start.md)

# AccessDeniedException 将您的图片上传到亚马逊 Elastic Container Registry (Amazon ECR) 时
<a name="working-with_custom-containers_troubleshooting_access-denied"></a>

如果您在尝试将容器映像上传到 Amazon ECR 时出现 `AccessDeniedException` 错误，则说明您的 IAM 身份（用户或角色）可能没有使用 Amazon ECR 所需的权限。您可以将`AmazonEC2ContainerRegistryPowerUser` AWS 托管策略附加到您的 IAM 身份，然后重试。有关如何附加策略的更多信息，请参阅《AWS Identity and Access Management 用户指南》中的[添加和删除 IAM 身份权限](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_manage-attach-detach.html)。**

# 使用自定义容器的模拟无法启动
<a name="working-with_custom-containers_troubleshooting_no-start"></a>

**问题排查技巧**
+ 如果为模拟启用了日志记录，请查看错误日志。
+ 在没有自定义容器的情况下测试模拟。
+ 在本地测试模拟。有关更多信息，请参阅 [当地发展 SimSpace Weaver](working-with_local-development.md)。

# 使用 Python
<a name="working-with_python"></a>

你可以将 Python 用于你的 SimSpace Weaver 应用程序和客户端。Python 软件开发套件 (Python SDK) 作为标准 SimSpace Weaver 应用程序 SDK 可分发包的一部分包含在内。使用 Python 进行开发与使用其他支持的语言进行开发的方式类似。

**重要**  
SimSpace Weaver 仅支持 Python 版本 3.9。

**重要**  
SimSpace Weaver 支持 Python 需要 SimSpace Weaver 版本 1.15.0 或更高版本。

**Topics**
+ [创建 Python 项目](working-with_python_create-project.md)
+ [启动 Python 模拟](working-with_python_start-sim.md)
+ [示例 Python 客户端](working-with_python_client.md)
+ [有关使用 Python 的常见问题解答](working-with_python_faq.md)
+ [排除与 Python 相关的问题](working-with_python_troubleshooting.md)

# 创建 Python 项目
<a name="working-with_python_create-project"></a>

## Python 自定义容器
<a name="working-with_python_create-project_container"></a>

要在中运行基于 Python 的 SimSpace Weaver 模拟 AWS 云，您可以创建一个包含必要依赖项的自定义容器。有关更多信息，请参阅 [自定义容器](working-with_custom-containers.md)。

Python 自定义容器必须包含以下内容：
+ gcc
+ openssl-devel
+ bzip2-devel
+ libffi-devel
+ wget
+ tar
+ gzip
+ make
+ Python（版本 3.9）

如果您使用 `PythonBubblesSample` 模板创建项目，则可以运行 `quick-start.py` 脚本（位于项目的 `tools` 文件夹中）来创建具有必要依赖项的 Docker 映像。该脚本会将映像上传到 Amazon Elastic Container Registry (Amazon ECR)。

`quick-start.py` 脚本使用以下 `Dockerfile`：

```
FROM public.ecr.aws/amazonlinux/amazonlinux:2
RUN yum -y install gcc openssl-devel bzip2-devel libffi-devel 
RUN yum -y install wget
RUN yum -y install tar
RUN yum -y install gzip
RUN yum -y install make
WORKDIR /opt
RUN wget https://www.python.org/ftp/python/3.9.0/Python-3.9.0.tgz 
RUN tar xzf Python-3.9.0.tgz
WORKDIR /opt/Python-3.9.0
RUN ./configure --enable-optimizations
RUN make altinstall
COPY requirements.txt ./
RUN python3.9 -m pip install --upgrade pip
RUN pip3.9 install -r requirements.txt
```

您可以将自己的依赖项添加到 `Dockerfile`：

```
RUN yum -y install dependency-name
```

`requirements.txt` 文件包含 `PythonBubblesSample` 示例模拟所需的 Python 程序包列表：

```
Flask==2.1.1
```

您可以将自己的 Python 程序包依赖项添加到 `requirements.txt`：

```
package-name==version-number
```

`Dockerfile` 和 `requirements.txt` 位于项目的 `tools` 文件夹中。

**重要**  
从技术上而言，您不必在 Python 模拟中使用自定义容器，但我们强烈建议您使用。我们提供的标准亚马逊 Linux 2 (AL2) 容器没有 Python。因此，如果您不使用包含 Python 的自定义容器，则必须在上传到的每个应用程序 zip 文件中包含 Python 和所需的依赖项 SimSpace Weaver。

# 启动 Python 模拟
<a name="working-with_python_start-sim"></a>

你可以像普通 SimSpace Weaver 仿真一样启动基于 Python 的模拟，两者都在 SimSpace Weaver Local 然后 SimSpace Weaver 在 AWS 云. 有关更多信息，请参阅中的教程[入门 SimSpace Weaver](getting-started.md)。

`PythonBubblesSample` 包含自己的 Python 示例客户端。有关更多信息，请参阅 [示例 Python 客户端](working-with_python_client.md)。

# 示例 Python 客户端
<a name="working-with_python_client"></a>

如果您使用 `PythonBubblesSample` 模板创建项目，则您的项目将包含一个 Python 示例客户端。您可以使用该示例客户端来查看 `PythonBubblesSample` 模拟。您也可以使用该示例客户端作为起点来创建自己的 Python 客户端。

以下过程假设您已创建 `PythonBubblesSample` 项目并启动了它的模拟。

**启动 Python 客户端**

1. 在**命令提示符窗口中**，转到`PyBubbleClient`示例项目文件夹。

   ```
   cd sdk-folder\Clients\HTTP\PyBubbleClient
   ```

1. 运行 Python 客户端。

   ```
   python tkinter_client.py --host ip-address --port port-number
   ```

**参数**  
**host**  
模拟的 IP 地址。对于在中启动的模拟 AWS 云，可以在[SimSpace Weaver 控制台](https://console.aws.amazon.com/simspaceweaver)中找到模拟的 IP 地址或使用快速入门教程中的[获取定制化应用程序的 IP 地址和端口号获取 IP 地址和端口号](working-with_get-ip.md)步骤。对于本地模拟，请使用 `127.0.0.1` 作为 IP 地址。  
**port**  
模拟的端口号。对于在中启动的模拟 AWS 云，这是`Actual`端口号。您可以在 [SimSpace Weaver 控制台](https://console.aws.amazon.com/simspaceweaver)中找到模拟的端口号，也可以按照快速入门教程中[获取定制化应用程序的 IP 地址和端口号获取 IP 地址和端口号](working-with_get-ip.md)的过程操作。对于本地模拟，请使用 `7000` 作为端口号。  
**simsize**  
要在客户端中显示的最大实体数。

# 有关使用 Python 的常见问题解答
<a name="working-with_python_faq"></a>

## 问题 1：哪些版本的 Python 受支持？
<a name="working-with_python_faq_q1"></a>

SimSpace Weaver 仅支持 Python 版本 3.9。

# 排除与 Python 相关的问题
<a name="working-with_python_troubleshooting"></a>

**Topics**
+ [创建自定义容器时失败](working-with_python_troubleshooting_create-container-failure.md)
+ [Python 模拟无法启动](working-with_python_troubleshooting_no-start.md)
+ [Python 模拟或视图客户端会引发错误 ModuleNotFound](working-with_python_troubleshooting_module-not-found.md)

# 创建自定义容器时失败
<a name="working-with_python_troubleshooting_create-container-failure"></a>

如果您在运行 `quick-start.py` 后出现错误 `no basic auth credentials`，则您的 Amazon ECR 临时凭证可能有问题。使用您的 AWS 区域 ID 和 AWS 账号运行以下命令：

```
aws ecr get-login-password --region region | docker login --username AWS --password-stdin account_id.dkr.ecr.region.amazonaws.com
```

**Example**  

```
aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin 111122223333.dkr.ecr.region.amazonaws.com
```

**重要**  
请确保 AWS 区域 您指定的与您用于模拟的相同。使用其中一个 SimSpace Weaver 支持 AWS 区域 的。有关更多信息，请参阅 [SimSpace Weaver 端点和配额](service-quotas.md)。

运行 `aws ecr` 命令后，再次运行 `quick-start.py`。

**其他需要查看的故障排除资源**
+ [自定义容器故障排除](working-with_custom-containers_troubleshooting.md)
+ *《Amazon ECR 用户指南》*中的 [Amazon ECR 故障排除](https://docs.aws.amazon.com/AmazonECR/latest/userguide/troubleshooting.html)
+ *《Amazon ECR 用户指南》*中的[对 Amazon ECR 进行设置](https://docs.aws.amazon.com/AmazonECR/latest/userguide/get-set-up-for-amazon-ecr.html)

# Python 模拟无法启动
<a name="working-with_python_troubleshooting_no-start"></a>

您可能会在模拟的管理日志中看到 `Unable to start app` 错误。如果您的自定义容器创建失败，则可能会发生这种情况。有关更多信息，请参阅 [创建自定义容器时失败](working-with_python_troubleshooting_create-container-failure.md)。有关日志的更多信息，请参阅[SimSpace Weaver 在 Amazon CloudWatch 日志中登录](cloudwatch-logs.md)。

如果您确定容器没有问题，请查看应用程序的 Python 源代码。您可以使用 … SimSpace Weaver Local 来测试你的应用程序。有关更多信息，请参阅 [当地发展 SimSpace Weaver](working-with_local-development.md)。

# Python 模拟或视图客户端会引发错误 ModuleNotFound
<a name="working-with_python_troubleshooting_module-not-found"></a>

当 Python 找不到所需的 Python 程序包时，则会出现 `ModuleNotFound` 错误。

如果您的模拟在中 AWS 云，请确保您的自定义容器中列出了所有必需的依赖项`requirements.txt`。如果您对 `requirements.txt` 进行了编辑，请记住再次运行 `quick-start.py`。

如果出现有关 `PythonBubblesSample` 客户端的错误，请使用 `pip` 安装指示的软件包：

```
pip install package-name==version-number
```

# 对其他引擎的支持
<a name="working-with_engines"></a>

你可以使用自己的自定义 C\$1\$1 带有 SimSpace Weaver. 的发动机 目前，我们正在开发对以下引擎的支持。每个引擎都有单独的文档。

**重要**  
与此处所列引擎的集成是实验性的。它们可供预览。

**引擎**
+ [Unity](#working-with_engines_unity)（最低版本 2022.3.19.F1）
+ [Unreal Engine](#working-with_engines_unreal)（最低版本 5.0）

## Unity
<a name="working-with_engines_unity"></a>

你必须有 Unity 在使用 Unity 构建 SimSpace Weaver 模拟之前，已经安装了开发环境。有关更多信息，请参阅单独的说明：

```
sdk-folder\Unity-Guide.pdf
```

## Unreal Engine
<a name="working-with_engines_unreal"></a>

你必须建一个 Unreal Engine 源代码中的专用服务器。 SimSpaceWeaverAppSdkDistributable 包括 for PathfindingSample 的版本 Unreal Engine。 有关更多信息，请参阅单独的说明：

```
sdk-folder\Unreal-Engine-Guide.pdf
```

# 将许可软件用于 AWS SimSpace Weaver
<a name="working-with_byol"></a>

AWS SimSpace Weaver 允许您使用自己选择的仿真引擎和内容来构建模拟。在使用时 SimSpace Weaver，您有责任获取、维护和遵守您在模拟中使用的任何软件或内容的许可条款。确认您的许可协议允许在虚拟托管环境中部署软件和内容。

# 使用管理您的资源 AWS CloudFormation
<a name="working-with_cloudformation"></a>

您可以使用 AWS CloudFormation 来管理您的 AWS SimSpace Weaver 资源。 CloudFormation 是一项单独的 AWS 服务，可帮助您以代码形式指定、配置和管理 AWS 基础架构。通过 CloudFormation 创建名为*[模板](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-whatis-concepts.html#cfn-concepts-templates template)*的 JSON 或 YAML 文件。您的模板指定了基础设施的详细信息。 CloudFormation 使用您的模板将基础架构配置为一个单元，称为*[堆栈](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-whatis-concepts.html#w2ab1b5c15b9)*。当您删除堆栈时，您可以同时 CloudFormation 删除堆栈中的所有内容。您可以使用标准源代码管理流程来管理模板（例如，在像 [Git](https://git-scm.com/) 这样的版本控制系统中进行跟踪）。有关的更多信息 CloudFormation，请参阅《[https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide)。

**模拟资源**  
在中 AWS，*资源*是您可以使用的实体。示例包括亚马逊 EC2 实例、Amazon S3 存储桶或 IAM 角色。您的 SimSpace Weaver 模拟是一种资源。在配置中，您通常在表单中指定 AWS 资源`AWS::service::resource`。对于 SimSpace Weaver，您可以将模拟资源指定为`AWS::SimSpaceWeaver::Simulation`。有关您的模拟资源的更多信息 CloudFormation，请参阅《*AWS CloudFormation 用户指南*》中的[SimSpace Weaver](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-simspaceweaver-simulation.html)部分。

**我该如何 CloudFormation 搭配使用 SimSpace Weaver？**  
您可以创建一个 CloudFormation 模板来指定要置备的 AWS 资源。您的模板可以指定整个架构、架构的一部分或小型解决方案。例如，您可以为 SimSpace Weaver 解决方案指定一个架构，其中包括 Amazon S3 存储桶、IAM 权限、Amazon Relational Database Service 或 Amazon DynamoDB 中的支持数据库以及您的资源。`Simulation`然后，您可以使用 CloudFormation 将所有这些资源作为一个单元同时置备。

**Example 创建 IAM 资源并启动模拟的示例模板**  
以下示例模板创建 SimSpace Weaver 在您的账户中执行操作所需的 IAM 角色和权限。当你创建项目 AWS 区域 时， SimSpace Weaver App SDK 脚本会创建特定角色和权限，但你可以使用 CloudFormation 模板将模拟部署到另一个模拟， AWS 区域 而无需再次运行脚本。例如，您可以这样设置用于灾难恢复目的的备份模拟。  
在本示例中，原始模拟名为 `MySimulation`。架构的存储桶已存在于构建堆栈 CloudFormation 的 AWS 区域 位置中。该存储桶包含已正确配置为在 AWS 区域中运行模拟的架构版本。回想一下，该架构指定了应用程序 zip 文件的位置，该文件是与模拟 AWS 区域 位于相同 中的 Amazon S3 存储桶。应用程序压缩存储桶和文件在 CloudFormation 构建堆栈 AWS 区域 时必须已经存在，否则您的模拟将无法启动。请注意，此示例中的存储桶名称包括 AWS 区域，但这并不能确定存储桶的实际位置。您必须确保存储桶实际上位于该存储桶中 AWS 区域 （您可以在 Amazon S3 控制台、使用 Amazon S3 或使用中的 APIs Amazon S3 命令查看存储桶属性 AWS CLI）。  
此示例使用中的一些内置函数和参数 CloudFormation 来执行变量替换。有关更多信息，请参阅《AWS CloudFormation 用户指南》中的[内置函数参考](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html)和[伪参数参考](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/pseudo-parameter-reference.html)。**  

```
AWSTemplateFormatVersion: 2010-09-09
Resources:
  WeaverAppRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: SimSpaceWeaverAppRole
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
        - Effect: Allow
          Principal:
            Service:
              - simspaceweaver.amazonaws.com
          Action:
            - sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: SimSpaceWeaverAppRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
            - Effect: Allow
              Action:
                - logs:PutLogEvents
                - logs:DescribeLogGroups
                - logs:DescribeLogStreams
                - logs:CreateLogGroup
                - logs:CreateLogStream
              Resource: *
            - Effect: Allow
              Action:
                - cloudwatch:PutMetricData
              Resource: *
            - Effect: Allow
              Action:
                - s3:ListBucket
                - s3:PutObject
                - s3:GetObject
              Resource: *
  MyBackupSimulation:
    Type: AWS::SimSpaceWeaver::Simulation
    Properties:
      Name: !Sub 'mySimulation-${AWS::Region}'
      RoleArn: !GetAtt WeaverAppRole.Arn
      SchemaS3Location:
        BucketName: !Sub 'weaver-mySimulation-${AWS::AccountId}-schemas-${AWS::Region}'
        ObjectKey: !Sub 'schema/mySimulation-${AWS::Region}-schema.yaml'
```

# 将快照用于 AWS CloudFormation
<a name="working-with_cloudformation_snapshots"></a>

[快照](working-with_snapshots.md)是模拟的备份。以下示例从快照启动新的模拟，而不是从架构启动。此示例中的快照是根据 SimSpace Weaver 应用程序 SDK 项目模拟创建的。 CloudFormation 创建新的仿真资源并使用快照中的数据对其进行初始化。新模拟可能与原始模拟的 `MaximumDuration` 不同。

我们建议您创建并使用原始模拟应用程序角色的副本。如果您删除原始模拟的 CloudFormation 堆栈，则可能会删除该模拟的应用程序角色。

```
Description: "Example - Start a simulation from a snapshot"
Resources:
  MyTestSimulation:
    Type: "AWS::SimSpaceWeaver::Simulation"
    Properties:
      MaximumDuration: "2D"
      Name: "MyTestSimulation_from_snapshot"
      RoleArn: "arn:aws:iam::111122223333:role/weaver-MyTestSimulation-app-role-copy"   
      SnapshotS3Location:
        BucketName: "weaver-mytestsimulation-111122223333-artifacts-us-west-2"
        ObjectKey: "snapshot/MyTestSimulation_22-12-15_12_00_00-230428-1207-13.zip"
```

# 快照
<a name="working-with_snapshots"></a>

您可以创建*快照*来随时备份模拟实体数据。 SimSpace Weaver 在 Amazon S3 存储桶中创建 .zip 文件。您可以使用快照创建新的模拟。 SimSpace Weaver 使用快照中存储的实体数据初始化新模拟的 State Fabric，启动创建快照时正在运行的空间和服务应用程序，并将时钟设置为相应的刻度。 SimSpace Weaver 从快照中获取模拟配置，而不是从架构文件中获取。您的应用程序 .zip 文件在 Amazon S3 中的位置必须与原始模拟中的位置相同。您必须单独启动任何自定义应用程序。

**主题**
+ [快照的使用案例](#working-with_snapshots_use-cases)
+ [使用 SimSpace Weaver 控制台处理快照](working-with_snapshots_console.md)
+ [使用 AWS CLI 来处理快照](working-with_snapshots_cli.md)
+ [将快照用于 AWS CloudFormation](working-with_cloudformation_snapshots.md)
+ [有关快照的常见问题解答](working-with_snapshots_faq.md)

## 快照的使用案例
<a name="working-with_snapshots_use-cases"></a>

### 返回到之前的状态并探索分支方案
<a name="working-with_snapshots_use-case_branching"></a>

您可以创建模拟快照以将其保存在特定状态下。然后，您可以从该快照创建多个新的模拟，并探索可能从该状态分支的不同方案。

### 灾难恢复和安全最佳实践
<a name="working-with_snapshots_use-cases_best-practice"></a>

我们建议您定期备份模拟，特别是对于运行时间超过 1 小时或使用多个工作线程的模拟。备份可以帮助您从灾难和安全事件中恢复。快照为您提供了一种备份模拟的方法。快照要求您的应用程序 .zip 文件与之前一样存在于 Amazon S3 中的相同位置。如果您需要能够将应用程序 .zip 文件移到其他位置，则必须使用自定义备份解决方案。

有关其他最佳实践的更多信息，请参阅[使用时的最佳实践 SimSpace Weaver](best-practices.md)和[以下方面的安全最佳实践 SimSpace Weaver](security_best-practices.md)。

### 延长模拟的持续时间
<a name="working-with_snapshots_use-cases_extend-duration"></a>

*模拟资源*是您的模拟在 SimSpace Weaver中的表示形式。所有模拟资源都有一个 `MaximumDuration` 设置。当模拟资源到达 `MaximumDuration` 时，它会自动停止。`MaximumDuration` 的最大值为 `14D`（14 天）。

如果您需要模拟的持续时间超过其模拟资源的 `MaximumDuration`，则可以在模拟资源达到 `MaximumDuration` 之前创建快照。您可以使用快照启动新的模拟（创建新的模拟资源）。 SimSpace Weaver 从快照初始化实体数据，启动之前运行的相同空间和服务应用程序，并恢复时钟。您可以启动自定义应用程序并执行任何其他自定义初始化。启动新模拟资源时，可以将 `MaximumDuration` 设置为不同的值。

# 使用 SimSpace Weaver 控制台处理快照
<a name="working-with_snapshots_console"></a>

您可以使用 SimSpace Weaver 控制台创建模拟的快照。

**Topics**
+ [创建快照](#working-with_snapshots_console_create)
+ [从快照启动模拟](#working-with_snapshots_console_start)

## 使用控制台创建快照
<a name="working-with_snapshots_console_create"></a>

**创建快照**

1. 登录 AWS 管理控制台 并连接到主[SimSpace Weaver 机](https://console.aws.amazon.com/simspaceweaver)。

1. 从导航窗格中选择**模拟**。

1. 选定模拟名称旁的单选按钮。模拟的**状态**必须为**已启动**。

1. 在页面顶部，选择**创建快照**。

1. 在 **“快照设置”** 下的 “**快照目标**” 中，输入要 SimSpace Weaver 在其中创建快照的存储桶或存储桶和文件夹的 Amazon S3 URI。如果您更喜欢浏览可用的存储桶并选择位置，则可以选择**浏览 S3**。
**重要**  
Amazon S3 存储桶必须与模拟位于同一 AWS 区域 中。
**注意**  
SimSpace Weaver 在选定的快照目标位置内创建一个`snapshot`文件夹。 SimSpace Weaver 在该`snapshot`文件夹中创建快照.zip 文件。

1. 选择**创建快照**。

## 使用控制台从快照启动模拟
<a name="working-with_snapshots_console_start"></a>

要从快照启动模拟，您的快照 .zip 文件必须存在于模拟可访问的 Amazon S3 存储桶中。您的模拟使用在启动模拟时选择的应用程序角色中定义的权限。原始模拟中的所有应用程序 .zip 文件必须与创建快照时位于相同的位置。

**从快照启动模拟**

1. 登录 AWS 管理控制台 并连接到主[SimSpace Weaver 机](https://console.aws.amazon.com/simspaceweaver)。

1. 从导航窗格中选择**模拟**。

1. 在页面顶部，选择**启动模拟**。

1. 在**模拟设置**下，输入模拟的名称和可选描述。模拟名称在 AWS 账户中必须是唯一的。

1. 对于**模拟启动方法**，请选择**使用 Amazon S3 中的快照**。

1. 对于**快照的 Amazon S3 URI**，请输入快照文件的 Amazon S3 URI，或者选择**浏览 S3**，以便浏览并选择该文件。
**重要**  
Amazon S3 存储桶必须与模拟位于同一 AWS 区域 中。

1. 对于 **IAM 角色**，请选择您的模拟将使用的应用程序角色。

1. 在**最长持续时间**中，请输入您的模拟资源应运行的最长时间。最大值为 `14D`。有关最长持续时间的更多信息，请参阅[。](https://docs.aws.amazon.com/simspaceweaver/latest/APIReference/API_StartSimulation.html)

1. 如果要添加标签，请在**标签 - *可选***下，选择**添加新标签**。

1. 选择**启动模拟**。

# 使用 AWS CLI 来处理快照
<a name="working-with_snapshots_cli"></a>

您可以使用在 AWS CLI 命令提示符 SimSpace Weaver APIs 下调用。您必须正确 AWS CLI 安装和配置。有关更多信息，请参阅[版本 *2 AWS Command Line Interface 用户指南中的安装或更新最新版本的 AWS * CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)。

**Topics**
+ [创建快照](#working-with_snapshots_cli_create)
+ [从快照启动模拟](#working-with_snapshots_cli_start)

## 使用 AWS CLI 创建快照
<a name="working-with_snapshots_cli_create"></a>

**创建快照**
+ 在**命令提示符**下，调用 `CreateSnapshot` API。

  ```
  aws simspaceweaver create-snapshot --simulation simulation-name —destination s3-destination
  ```

  **参数**  
模拟  
启动的模拟的名称。您可以使用 `aws simspaceweaver list-simulations` 来查看模拟的名称和状态。  
destination  
一个字符串，用于为您的快照文件指定目标 Amazon S3 存储桶和可选的对象键前缀。您的对象 key prefix 通常是存储桶中的一个文件夹。 SimSpace Weaver 在此目标的`snapshot`文件夹中创建您的快照。  
Amazon S3 存储桶必须与模拟位于同一 AWS 区域 中。

  **示例**

  ```
  aws simspaceweaver create-snapshot —simulation MyProjectSimulation_23-04-29_12_00_00 —destination BucketName=weaver-myproject-111122223333-artifacts-us-west-2,ObjectKeyPrefix=myFolder
  ```

有关 `CreateSnapshot` API 的更多信息，请参阅 *AWS SimSpace Weaver API 参考[CreateSnapshot](https://docs.aws.amazon.com/simspaceweaver/latest/APIReference/API_CreateSnapshot.html)*中的。

## 使用 AWS CLI 从快照开始模拟
<a name="working-with_snapshots_cli_start"></a>

**从快照启动模拟**
+ 在**命令提示符**下，调用 `StartSimulation` API。

  ```
  aws simspaceweaver start-simulation --name simulation-name --role-arn role-arn --snapshot-s3-location s3-location
  ```

  **参数**  
name  
新作业的名称。您的模拟名称必须是唯一的 AWS 账户。您可以使用 `aws simspaceweaver list-simulations` 来查看现有模拟的名称。  
role-arn  
您的模拟将使用的 Amazon 资源名称 (ARN)。  
snapshot-s3-location  
一个字符串，用于为您的快照文件指定 Amazon S3 存储桶和对象键。  
Amazon S3 存储桶必须与模拟位于同一 AWS 区域 中。

  **示例**

  ```
  aws simspaceweaver start-simulation —name MySimulation —role-arn arn:aws:iam::111122223333:role/weaver-MyProject-app-role —snapshot-s3-location BucketName=weaver-myproject-111122223333-artifacts-us-west-2,ObjectKey=myFolder/snapshot/MyProjectSimulation_23-04-29_12_00_00-230429-1530-27.zip
  ```

有关 `StartSimulation` API 的更多信息，请参阅 *AWS SimSpace Weaver API 参考[StartSimulation](https://docs.aws.amazon.com/simspaceweaver/latest/APIReference/API_StartSimulation.html)*中的。

# 有关快照的常见问题解答
<a name="working-with_snapshots_faq"></a>

**在创建快照时，我的模拟会继续运行吗？**  
在创建快照时，您的模拟资源会继续运行，并且您需要继续为这段时间支付账单费用。这段时间也会计入模拟的最长持续时间。正在创建快照时，您的应用程序不会收到时间周期。开始创建快照后，如果您的时钟状态为 `STARTED`，则仍会显示 `STARTED` 状态。快照创建完成后，您的应用程序会再次收到时间周期。如果您的时钟状态为 `STOPPED`，则会保持为 `STOPPED`。请注意，即使时钟状态为 `STOPPED`，具有 `STARTED` 状态的模拟也仍在运行。

**如果正在创建快照，并且我的模拟已达到最长持续时间，会怎样？**  
您的模拟将完成快照创建，然后在快照过程结束（无论是成功还是失败）后立即停止。我们建议您预先测试快照过程，了解需要多长时间、预计的快照文件大小以及能否成功完成。

**如果我停止正在创建快照的模拟，会怎样？**  
停止模拟后，正在创建的快照会立即停止。它不会创建快照文件。

**如何停止正在创建的快照？**  
要停止正在创建的快照，唯一方法是停止模拟。**模拟一旦停止，将无法重新启动。**

**完成创建快照需要多长时间？**  
创建快照所需的时间取决于您的模拟。我们建议您预先测试快照过程，了解您的模拟需要多长时间。

**我的快照文件会有多大？**  
快照文件的大小取决于您的模拟。我们建议您预先测试快照过程，了解您的模拟的快照文件可能有多大。

# 消息收发
<a name="working-with_messaging"></a>

消息传递 API 简化了仿真中应用程序与应用程序的通信。 APIs 发送和接收消息是 SimSpace Weaver 应用程序 SDK 的一部分。消息传递目前使用尽力发送和接收消息。 SimSpace Weaver 尝试在下一次模拟报价时 send/receive 发送消息，但没有送达、订购或到达时间保证。

**主题**
+ [消息传递用例](#working-with_messaging_use-cases)
+ [使用消息 APIs](working-with_messaging_using.md)
+ [何时使用消息传递](working-with_messaging_when-to-use.md)
+ [处理消息传递时的提示](working-with_messaging_tips.md)
+ [消息错误和疑难解答](working-with_messaging_troubleshooting.md)

## 消息传递用例
<a name="working-with_messaging_use-cases"></a>

**在仿真应用程序之间进行通信**  
在模拟中，使用消息传递 API 在应用程序之间进行通信。使用它可以在一定距离内更改实体的状态、更改实体行为或向整个仿真广播信息。

**确认收到消息**  
已发送的消息在邮件标题中包含有关发件人的信息。收到消息后，使用此信息发回确认回复。

**将定制化 App 接收到的数据转发到仿真中的其他应用程序**  
消息传递不能取代客户端连接到中运行的自定义应用程序的方式 SimSpace Weaver。但是，消息传递确实允许用户将接收客户端数据的自定义应用程序中的数据转发到其他没有外部连接的应用程序。消息流也可以反向工作，允许没有外部连接的应用程序将数据转发到自定义应用程序，然后再转发到客户端。

# 使用消息 APIs
<a name="working-with_messaging_using"></a>

消息包含 APIs 在 SimSpace Weaver 应用程序 SDK（最低版本 1.16.0）中。C\$1\$1、Python 以及我们与虚幻引擎 5 和 Unity 的集成都支持消息传递。

有两个函数可以处理消息事务：`SendMessage`和`ReceiveMessages`。所有发送的消息都包含目的地和有效负载。`ReceiveMessages`API 会返回应用程序入站消息队列中当前的消息列表。

------
#### [ C\$1\$1 ]

**发送消息**

```
AWS_WEAVERRUNTIME_API Result<void> SendMessage(
    Transaction& txn,
    const MessagePayload& payload,
    const MessageEndpoint& destination,
    MessageDeliveryType deliveryType = MessageDeliveryType::BestEffort
    ) noexcept;
```

**接收消息**

```
AWS_WEAVERRUNTIME_API Result<MessageList> ReceiveMessages(
    Transaction& txn) noexcept;
```

------
#### [ Python ]

**发送消息**

```
api.send_message(
 txn, # Transaction
 payload, # api.MessagePayload
 destination, # api.MessageDestination
 api.MessageDeliveryType.BestEffort # api.MessageDeliveryType
)
```

**接收消息**

```
api.receive_messages(
 txn, # Transaction
) -> api.MessageList
```

------

**主题**
+ [发送消息](#working-with_messaging_using_send)
+ [接收消息](#working-with_messaging_using_receive)
+ [回复发件人](#working-with_messaging_using_reply)

## 发送消息
<a name="working-with_messaging_using_send"></a>

消息由交易（类似于其他 Weaver API 调用）、有效负载和目标组成。

### 消息负载
<a name="working-with_messaging_using_send_payload"></a>

消息有效载荷是一种灵活的数据结构，最多 256 字节。我们建议将以下作为创建消息负载的最佳实践。

**创建消息负载**

1. 创建定义消息内容的数据结构（例如 C\$1\$1 `struct` 中的）。

1. 创建包含要在消息中发送的值的消息负载。

1. 创建 `MessagePayload` 对象。

### 消息目的地
<a name="working-with_messaging_using_send_destination"></a>

消息的目的地由`MessageEndpoint`对象定义。这包括端点类型和终端节点 ID。目前唯一支持的端点类型是`Partition`，它允许您在模拟中将消息发送到其他分区。终端节点 ID 是目标目标的分区 ID。

您只能在一封邮件中提供 1 个目标地址。如果您想同时向多个分区发送消息，请创建并发送多条消息。

有关如何从某个位置解析消息端点的指导，请参阅[处理消息传递时的提示](working-with_messaging_tips.md)。

### 发送消息
<a name="working-with_messaging_using_send_send"></a>

您可以在创建目标和负载对象之后使用 `SendMessage` API。

------
#### [ C\$1\$1 ]

```
Api::SendMessage(transaction, payload, destination, MessageDeliveryType::BestEffort);
```

------
#### [ Python ]

```
api.send_message(txn, payload, destination, api.MessageDeliveryType.BestEffort)
```

------

**发送消息的完整示例**  
以下示例演示了如何构造和发送通用消息。此示例发送了 16 条单独的消息。每条消息都包含一个值介于 0 和 15 之间的有效载荷以及当前的模拟滴答声。

**Example**  

```
// Message struct definition
struct MessageTickAndId
{
    uint32_t id;
    uint32_t tick;
};

Aws::WeaverRuntime::Result<void> SendMessages(Txn& txn) noexcept
{
     // Fetch the destination MessageEndpoint with the endpoint resolver
    WEAVERRUNTIME_TRY(
        Api::MessageEndpoint destination,
        Api::Utils::MessageEndpointResolver::ResolveFromPosition(
        txn,
            "MySpatialSimulation",
            Api::Vector2F32 {231.3, 654.0}
        )
    );
    Log::Info("destination: ", destination);

    WEAVERRUNTIME_TRY(auto tick, Api::CurrentTick(txn));

    uint16_t numSentMessages = 0;
    for (std::size_t i=0; i<16; i++)
    {
        // Create the message that'll be serialized into payload
        MessageTickAndId message {i, tick.value};
        
        // Create the payload out of the struct
        const Api::MessagePayload& payload = Api::Utils::CreateMessagePayload(
            reinterpret_cast<const std::uint8_t*>(&message), 
            sizeof(MessageTickAndId)
        );
        
        // Send the payload to the destination
        Result<void> result = Api::SendMessage(txn, payload, destination);
        if (result.has_failure())
        {
            // SendMessage has failure modes, log them
            auto error = result.as_failure().error();
            std::cout<< "SendMessage failed, ErrorCode: " << error << std::endl;
            continue;
        }
        
        numSentMessages++;
    }

    std::cout << numSentMessages << " messages is sent to endpoint" 
       << destination << std::endl;
    return Aws::WeaverRuntime::Success();
}
```

```
# Message data class
@dataclasses.dataclass
class MessageTickAndId:
    tick: int = 0
    id: int = 0
    
# send messages
def _send_messages(self, txn):
    tick = api.current_tick(txn)
    num_messages_to_send = 16

    # Fetch the destination MessageEndpoint with the endpoint resolver
    destination = api.utils.resolve_endpoint_from_domain_name_position(
       txn,
       "MySpatialSimulation",
       pos
   )
    Log.debug("Destination_endpoint = %s", destination_endpoint)

   for id in range(num_messages_to_send):
       # Message struct that'll be serialized into payload
        message_tick_and_id = MessageTickAndId(id = id, tick = tick.value)
        
       # Create the payload out of the struct
        message_tick_and_id_data = struct.pack(
           '<ii',
           message_tick_and_id.id,
           message_tick_and_id.tick
       )
        payload = api.MessagePayload(list(message_tick_and_id_data))

        # Send the payload to the destination
        Log.debug("Sending message: %s, endpoint: %s",
           message_tick_and_id,
           destination
       )
        api.send_message(
           txn,
           payload,
           destination,
           api.MessageDeliveryType.BestEffort
       )

    Log.info("Sent %s messages to %s", num_messages_to_send, destination)
    return True
```

## 接收消息
<a name="working-with_messaging_using_receive"></a>

SimSpace Weaver 将消息传送到分区的入站消息队列中。使用 `ReceiveMessages` API 获取包含队列消息的`MessageList`对象。使用 `ExtractMessage` API 处理每条消息以获取消息数据。

**Example**  

```
Result<void> ReceiveMessages(Txn& txn) noexcept
{
     // Fetch all the messages sent to the partition owned by the app
    WEAVERRUNTIME_TRY(auto messages, Api::ReceiveMessages(txn));
    std::cout << "Received" << messages.messages.size() << " messages" << std::endl;
    for (Api::Message& message : messages.messages)
    {
        std::cout << "Received message: " << message << std::endl;

         // Deserialize payload to the message struct
        const MessageTickAndId& receivedMessage 
            = Api::Utils::ExtractMessage<MessageTickAndId>(message);
        std::cout << "Received MessageTickAndId, Id: " << receivedMessage.id 
            <<", Tick: " << receivedMessage.tick << std::endl;
    }

    return Aws::WeaverRuntime::Success();
}
```

```
# process incoming messages
def _process_incoming_messages(self, txn):
    messages = api.receive_messages(txn)
    for message in messages:
        payload_list = message.payload.data
        payload_bytes = bytes(payload_list)
        message_tick_and_id_data_struct 
           = MessageTickAndId(*struct.unpack('<ii', payload_bytes))

        Log.debug("Received message. Header: %s, message: %s", 
                    message.header, message_tick_and_id_data_struct)

    Log.info("Received %s messages", len(messages))
    return True
```

## 回复发件人
<a name="working-with_messaging_using_reply"></a>

收到的每封邮件都包含一个邮件标头，其中包含有关邮件原始发件人的信息。你可以使用 message.header.source\$1endpoint 发送回复。

**Example**  

```
Result<void> ReceiveMessages(Txn& txn) noexcept
{
     // Fetch all the messages sent to the partition owned by the app
    WEAVERRUNTIME_TRY(auto messages, Api::ReceiveMessages(txn));
    std::cout << "Received" << messages.messages.size() << " messages" << std::endl;
    for (Api::Message& message : messages.messages)
    {
        std::cout << "Received message: " << message << std::endl;

         // Deserialize payload to the message struct
        const MessageTickAndId& receivedMessage 
            = Api::Utils::ExtractMessage<MessageTickAndId>(message);
        std::cout << "Received MessageTickAndId, Id: " << receivedMessage.id 
            <<", Tick: " << receivedMessage.tick << std::endl;
        
        // Get the sender endpoint and payload to bounce the message back
        Api::MessageEndpoint& sender = message.header.source_endpoint;
        Api::MessagePayload& payload = message.payload;
        Api::SendMessage(txn, payload, sender);
    }

    return Aws::WeaverRuntime::Success();
}
```

```
# process incoming messages
def _process_incoming_messages(self, txn):
    messages = api.receive_messages(txn)
    for message in messages:
        payload_list = message.payload.data
        payload_bytes = bytes(payload_list)
        message_tick_and_id_data_struct 
           = MessageTickAndId(*struct.unpack('<ii', payload_bytes))

        Log.debug("Received message. Header: %s, message: %s", 
                    message.header, message_tick_and_id_data_struct)
       # Get the sender endpoint and payload 
       # to bounce the message back
       sender = message.header.source_endpoint
       payload = payload_list
       api.send_message(
           txn,
           payload_list,
           sender,
           api.MessageDeliveryType.BestEffort

    Log.info("Received %s messages", len(messages))
    return True
```

# 何时使用消息传递
<a name="working-with_messaging_when-to-use"></a>

中的消息传递为仿真应用程序之间交换信息 SimSpace Weaver 提供了另一种模式。订阅提供了一种拉取机制，用于从仿真的特定应用程序或区域读取数据；消息提供了一种推送机制，用于将数据发送到仿真的特定应用程序或区域。

以下是两个用例，在这些用例中，使用消息传递推送数据比通过订阅提取或读取数据更有帮助。

**Example 1：向其他应用程序发送命令以更改实体位置**  

```
// Message struct definition
struct MessageMoveEntity
{
     uint64_t entityId;
    std::array<float, 3> destinationPos;
};

// Create the message 
MessageMoveEntity message {45, {236.67, 826.22, 0.0} };

// Create the payload out of the struct
const Api::MessagePayload& payload = Api::Utils::CreateMessagePayload(
    reinterpret_cast<const std::uint8_t*>(&message), 
    sizeof(MessageTickAndId)
);

// Grab the MessageEndpoint of the recipient app.
Api::MessageEndpoint destination = ...

// One way is to resolve it from the domain name and position
WEAVERRUNTIME_TRY(
    Api::MessageEndpoint destination,
    Api::Utils::MessageEndpointResolver::ResolveFromPosition(
    txn,
        "MySpatialSimulation",
        Api::Vector2F32 {200.0, 100.0}
    )
);

// Then send the message 
Api::SendMessage(txn, payload, destination);
```
在接收方，应用程序更新实体的位置并将其写入State Fabric。  

```
Result<void> ReceiveMessages(Txn& txn) noexcept
{
    WEAVERRUNTIME_TRY(auto messages, Api::ReceiveMessages(txn));
    for (Api::Message& message : messages.messages)
    {
        std::cout << "Received message: " << message << std::endl;
         // Deserialize payload to the message struct
        const MessageMoveEntity& receivedMessage 
            = Api::Utils::ExtractMessage<MessageMoveEntity>(message);
            
        ProcessMessage(txn, receivedMessage);
    }

    return Aws::WeaverRuntime::Success();
}

void ProcessMessage(Txn& txn, const MessageMoveEntity& receivedMessage)
{
     // Get the entity corresponding to the entityId
    Entity entity = EntityFromEntityId (receivedMessage.entityId);
    
    // Update the position and write to StateFabric
    WEAVERRUNTIME_TRY(Api::StoreEntityIndexKey(
            txn,
            entity,
            k_vector3f32TypeId, // type id of the entity
            reinterpret_cast<std::int8_t*>(&receivedMessage.destinationPos),
            sizeof(receivedMessage.destinationPos)));
    
}
```

**Example 2：向空间应用程序发送创建实体消息**  

```
struct WeaverMessage
{
    const Aws::WeaverRuntime::Api::TypeId messageTypeId;
};

const Aws::WeaverRuntime::Api::TypeId k_createEntityMessageTypeId = { 1 };

struct CreateEntityMessage : WeaverMessage
{
    const Vector3 position;
   const Aws::WeaverRuntime::Api::TypeId typeId;
}; 


CreateEntityMessage messageData { 
    k_createEntityMessageTypeId,                           
    Vector3{ position.GetX(), position.GetY(), position.GetZ() },
    Api::TypeId { 0 }
}

WEAVERRUNTIME_TRY(Api::MessageEndpoint destination, Api::Utils::MessageEndpointResolver::ResolveFromPosition(
    transaction, "MySpatialDomain", DemoFramework::ToVector2F32(position)
));

Api::MessagePayload payload = Api::Utils::CreateMessagePayload(
    reinterpret_cast<const uint8_t*>(&messageData),
    sizeof(CreateEntityMessage));
        
Api::SendMessage(transaction, payload, destination);
```
在接收方，该应用程序在 State Fabric 中创建一个新实体并更新其位置。  

```
Result<void> ReceiveMessages(Txn& txn) noexcept
{
    WEAVERRUNTIME_TRY(auto messageList, Api::ReceiveMessages(transaction));
    WEAVERRUNTIME_TRY(auto tick, Api::CurrentTick(transaction));
    for (auto& message : messageList.messages)
    {
        // cast to base WeaverMessage type to determine MessageTypeId
        WeaverMessage weaverMessageBase = Api::Utils::ExtractMessage<WeaverMessage>(message);
        if (weaverMessageBase.messageTypeId == k_createEntityMessageTypeId)
        {
            CreateEntityMessage createEntityMessageData =
                Api::Utils::ExtractMessage<CreateEntityMessage>(message);
        CreateActorFromMessage(transaction, createEntityMessageData));
        }
        else if (weaverMessageBase.messageTypeId == k_tickAndIdMessageTypeId)
        {
            ...
        }
    }
}

void ProcessMessage(Txn& txn, const CreateEntityMessage& receivedMessage)
{
    // Create entity
    WEAVERRUNTIME_TRY(
        Api::Entity entity,
        Api::CreateEntity(transaction, receivedMessage.typeId)
    );
    
    // Update the position and write to StateFabric
    WEAVERRUNTIME_TRY(Api::StoreEntityIndexKey(
        transaction,
        entity,
        receivedMessage.typeId,
        reinterpret_cast<std::int8_t*>(&receivedMessage.position),
        sizeof(receivedMessage.position)));
}
```

# 处理消息传递时的提示
<a name="working-with_messaging_tips"></a>

## 根据位置或应用程序名称解析端点
<a name="working-with_messaging_tips_resolve-endpoint"></a>

您可以使用该`AllPartitions`函数获取空间边界和域 ID，以确定消息分 IDs区和消息目的地。但是，如果您知道要发送消息的位置，但不知道其分区 ID，则可以使用该 MessageEndpointResolver 函数。

```
/**
* Resolves MessageEndpoint's from various inputs
**/
class MessageEndpointResolver
{
    public:
    /**
    * Resolves MessageEndpoint from position information
    **/
    Result<MessageEndpoint> ResolveEndpointFromPosition(
        const DomainId& domainId,
        const weaver_vec3_f32_t& pos);

    /**
    * Resolves MessageEndpoint from custom app name
    **/
    Result<MessageEndpoint> ResolveEndpointFromCustomAppName(
        const DomainId& domainId,
        const char* agentName);
};
```

## 序列化和反序列化消息负载
<a name="working-with_messaging_tips_serialize-payload"></a>

您可以使用以下函数来创建和读取消息负载。有关更多信息，请参阅本地系统上应用程序 SDK 库中的 MessagingUtils .h。

```
/**
* Utility function to create MessagePayload from a custom type
*
* @return The @c MessagePayload.
*/
template <class T>
AWS_WEAVERRUNTIME_API MessagePayload CreateMessagePayload(const T& message) noexcept
{
    const std::uint8_t* raw_data = reinterpret_cast<const std::uint8_t*>(&message);

    MessagePayload payload;
    std::move(raw_data, raw_data + sizeof(T), std::back_inserter(payload.data));

    return payload;
}

/**
* Utility function to convert MessagePayload to custom type
*/
template <class T>
AWS_WEAVERRUNTIME_API T ExtractMessage(const MessagePayload& payload) noexcept
{
    return *reinterpret_cast<const T*>(payload.data.data());
}
```

# 消息错误和疑难解答
<a name="working-with_messaging_troubleshooting"></a>

使用消息时，您可能会遇到以下错误 APIs。

## 端点解析错误
<a name="working-with_messaging_troubleshooting_endpoint-resolution"></a>

这些错误可能发生在应用程序发送消息之前。

### 域名检查
<a name="working-with_messaging_troubleshooting_dns-check"></a>

向无效端点发送消息会导致以下错误：

```
ManifoldError::InvalidArgument {"No DomainId found for the given domain name" }
```

当您尝试向定制化 App 发送消息，但该定制化 App 尚未加入模拟时，就会发生这种情况。使用该 `DescribeSimulation` API 确保您的定制化应用程序已启动，然后再向其发送消息。这种行为在SimSpace Weaver Local和中是一样的 AWS 云。

### 位置检查
<a name="working-with_messaging_troubleshooting_position-check"></a>

尝试解析具有有效域名但位置无效的端点会导致以下错误。

```
ManifoldError::InvalidArgument {"Could not resolve endpoint from domain : DomainId { value: domain-id } and position: Vector2F32 { x: x-position, y: y-position}" }
```

我们建议使用 SimSpace Weaver 应用程序 SDK 中包含的`MessageUtils`库中的。`MessageEndpointResolver`

## 消息发送错误
<a name="working-with_messaging_troubleshooting_message-sending"></a>

应用程序发送消息时可能会出现以下错误。

### 已超过每个应用程序、每次点击的消息发送限制
<a name="working-with_messaging_troubleshooting_send-limit"></a>

目前，每个应用程序每次仿真跳动可以发送的消息数量限制为 128。同一报价的后续调用将失败，并出现以下错误：

```
ManifoldError::CapacityExceeded {"At Max Outgoing Message capacity: {}", 128}
```

SimSpace Weaver 尝试在下次勾选时发送未发送的消息。降低发送频率以解决此问题。合并小于 256 字节限制的消息负载，以减少出站消息的数量。

这种行为在中SimSpace Weaver Local和之中都是一样的 AWS 云。

### 已超出消息有效负载大小限制
<a name="working-with_messaging_troubleshooting_size-limit"></a>

当前消息有效载荷大小限制为 256 字节，两者SimSpace Weaver Local兼而有之 AWS 云。发送有效载荷大于 256 字节的消息会导致以下错误：

```
ManifoldError::CapacityExceeded {"Message data too large! Max size: {}", 256}
```

SimSpace Weaver 会检查每条消息，只拒绝超过限制的消息。例如，如果您的应用程序尝试发送 10 条消息，但有 1 条未通过检查，则只有这 1 条消息被拒绝。 SimSpace Weaver 发送其他 9 条消息。

这种行为在SimSpace Weaver Local和中是一样的 AWS 云。

### 目的地与源相同
<a name="working-with_messaging_troubleshooting_dst-src-same"></a>

应用程序无法向其拥有的分区发送消息。如果应用程序向其拥有的分区发送消息，则会出现以下错误。

```
ManifoldError::InvalidArgument { "Destination is the same as source" }
```

这种行为在SimSpace Weaver Local和中是一样的 AWS 云。

### 尽力发送消息
<a name="working-with_messaging_troubleshooting_best-effort"></a>

SimSpace Weaver 不能保证消息的传送。该服务将尝试在随后的模拟滴答中完成消息的传送，但消息可能会丢失或延迟。