This commit is contained in:
2025-08-02 12:09:34 +08:00
commit e70b01cdca
2785 changed files with 575579 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
# BlueprintAuthorityOnly
- **功能描述:** 这个函数只能在拥有网络权限的端上运行。
- **元数据类型:** bool
- **引擎模块:** Network
- **作用机制:** 在FunctionFlags中添加[FUNC_BlueprintAuthorityOnly](../../../../Flags/EFunctionFlags/FUNC_BlueprintAuthorityOnly.md)
- **常用程度:** ★★★
这个函数只能在拥有网络权限的端上运行。HasAuthority::GetLocalRole() == ROLE_Authority。共有4种NetRole: ROLE_None不复制ROLE_SimulatedProxy在客户端上模拟的代理ROLE_AutonomousProxy在客户端上的匿名代理接收玩家输入ROLE_Authority服务器拥有权限的
因此BlueprintAuthorityOnly限定这个函数只能在服务器上运行这个“服务器”可以是LS服务器DS服务器单机可以看作没有客户端的服务器
注意在测试的时候需要把该Actor设置为Replicates。
## 测试代码:
```cpp
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API AMyFunction_Network :public AActor
{
public:
GENERATED_BODY()
public:
//FunctionFlags: FUNC_Final | FUNC_Native | FUNC_Public | FUNC_BlueprintCallable
UFUNCTION(BlueprintCallable)
void MyFunc_Default();
//FunctionFlags: FUNC_Final | FUNC_BlueprintAuthorityOnly | FUNC_Native | FUNC_Public | FUNC_BlueprintCallable
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly)
void MyFunc_BlueprintAuthorityOnly();
static void PrintFuncStatus(AActor* actor,FString funcName);
};
void AMyFunction_Network::MyFunc_Default()
{
PrintFuncStatus(this,TEXT("MyFunc_Default"));
}
void AMyFunction_Network::MyFunc_BlueprintAuthorityOnly()
{
PrintFuncStatus(this,TEXT("MyFunc_BlueprintAuthorityOnly"));
}
void AMyFunction_Network::PrintFuncStatus(AActor* actor, FString funcName)
{
FString actorName = actor->GetName();
FString localRoleStr;
UEnum::GetValueAsString(actor->GetLocalRole(), localRoleStr);
FString remoteRoleStr;
UEnum::GetValueAsString(actor->GetRemoteRole(), remoteRoleStr);
FString netModeStr = Insider::NetModeToString(actor->GetNetMode());
FString str = FString::Printf(TEXT("%s\t%s\t%s\tLocal:%s\tRemote:%s"), *funcName,*actorName, *netModeStr, *localRoleStr, *remoteRoleStr);
GEngine->AddOnScreenDebugMessage(-1, 20.f, FColor::Red, str);
UE_LOG(LogInsider, Display, TEXT("%s"), *str);
}
```
## 蓝图代码:
![Untitled](Untitled.png)
对于不Replicated的Actor
```cpp
MyFunc_Default BP_Network_C_1 NM_ListenServer Local:ROLE_Authority Remote:ROLE_None
MyFunc_Default BP_Network_C_1 NM_Client Local:ROLE_None Remote:ROLE_Authority
MyFunc_Default BP_Network_C_1 NM_Client Local:ROLE_None Remote:ROLE_Authority
```
而对于Replicated的Actor同时有1个S和两个C运行普通的函数
```cpp
MyFunc_Default BP_Network_C_1 NM_ListenServer Local:ROLE_Authority Remote:ROLE_SimulatedProxy
MyFunc_Default BP_Network_C_1 NM_Client Local:ROLE_SimulatedProxy Remote:ROLE_Authority
MyFunc_Default BP_Network_C_1 NM_Client Local:ROLE_SimulatedProxy Remote:ROLE_Authority
```
如果允许的BlueprintAuthorityOnly函数
```cpp
MyFunc_BlueprintAuthorityOnly BP_Network_C_1 NM_ListenServer Local:ROLE_Authority Remote:ROLE_SimulatedProxy
```
结果可见Default的函数在3个端上都可以运行而BlueprintAuthorityOnly只能在服务器上运行。而Client上无法运行。
## 原理:
```cpp
int32 AActor::GetFunctionCallspace( UFunction* Function, FFrame* Stack )
{
FunctionCallspace::Type Callspace = (LocalRole < ROLE_Authority) && Function->HasAllFunctionFlags(FUNC_BlueprintAuthorityOnly) ? FunctionCallspace::Absorbed : FunctionCallspace::Local;
}
```

View File

@@ -0,0 +1,47 @@
# BlueprintCosmetic
- **功能描述:** 此函数为修饰性的无法在DS上运行。
- **元数据类型:** bool
- **引擎模块:** Network
- **作用机制:** 在FunctionFlags中加入[FUNC_BlueprintCosmetic](../../../../Flags/EFunctionFlags/FUNC_BlueprintCosmetic.md)
- **常用程度:** ★★★
这个函数是修饰性的所谓修饰性是指这个函数的内容是为了展现一些与逻辑无关的内容比如动画音效特效等。因为DS并没有实际的画面输出因此这些修饰性的函数是对DS无意义的。因此这些修饰性函数会被无视掉。
但是也注意在ListenServer或Client上这二者都会允许运行。因为这两个端都需要画面展示。
## 测试代码:
```cpp
UFUNCTION(BlueprintCallable, BlueprintCosmetic)
void MyFunc_BlueprintCosmetic();
```
## 测试蓝图:
节点上的电脑标记就是意味着只在客户端上运行。
![Untitled](Untitled.png)
结果输出
```cpp
MyFunc_BlueprintCosmetic BP_Network_C_1 NM_ListenServer Local:ROLE_Authority Remote:ROLE_SimulatedProxy
MyFunc_BlueprintCosmetic BP_Network_C_1 NM_Client Local:ROLE_SimulatedProxy Remote:ROLE_Authority
MyFunc_BlueprintCosmetic BP_Network_C_1 NM_Client Local:ROLE_SimulatedProxy Remote:ROLE_Authority
```
## 原理:
```cpp
int32 AActor::GetFunctionCallspace( UFunction* Function, FFrame* Stack )
{
// Dedicated servers don't care about "cosmetic" functions.
if (NetMode == NM_DedicatedServer && Function->HasAllFunctionFlags(FUNC_BlueprintCosmetic))
{
DEBUG_CALLSPACE(TEXT("GetFunctionCallspace Blueprint Cosmetic Absorbed: %s"), *Function->GetName());
return FunctionCallspace::Absorbed;
}
}
```

View File

@@ -0,0 +1,48 @@
# Client
- **功能描述:** 在Client-owned的Actor上PlayerController或Pawn执行一个RPC函数只运行在客户端上。对应的实现函数会添加_Implementation后缀。
- **元数据类型:** bool
- **引擎模块:** Network
- **作用机制:** 在FunctionFlags加入[FUNC_Net](../../../../Flags/EFunctionFlags/FUNC_Net.md)、[FUNC_NetClient](../../../../Flags/EFunctionFlags/FUNC_NetClient.md)
- **常用程度:★★★★★**
在Client-owned的Actor上PlayerController或Pawn执行一个RPC函数只运行在客户端上。对应的实现函数会添加_Implementation后缀。
一般用于从Server发送一个RPC到Client。和蓝图里RunOnClient的效果一样。
所谓Client-owned参考文档[https://docs.unrealengine.com/4.27/zh-CN/InteractiveExperiences/Networking/Actors/RPCs/](https://docs.unrealengine.com/4.27/zh-CN/InteractiveExperiences/Networking/Actors/RPCs/)
![Untitled](Untitled.png)
## 测试代码:
```cpp
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API AMyFunction_PlayerController :public APlayerController
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Client, Reliable)
void MyFunc_RunOnClient();
};
void AMyFunction_PlayerController::MyFunc_RunOnClient_Implementation()
{
UInsiderLibrary::PrintFuncStatus(this, TEXT("MyFunc_RunOnClient_Implementation"));
}
```
测试蓝图PIE模式一个ListenServer+2Client
![Untitled](Untitled%201.png)
## 测试输出结果:
```cpp
MyFunc_Client_Implementation BP_NetworkPC_C_0 NM_Client Local:ROLE_AutonomousProxy Remote:ROLE_Authority
OtherClientFunc BP_NetworkPC_C_0 NM_Client Local:ROLE_AutonomousProxy Remote:ROLE_Authority
```
可见测试代码中取第2个PC发出一个Run on Client的RPC调用最终在Client上成功触发。C++定义的函数和蓝图中添加的自定义RunOnClient事件效果是等价的。
而如果这个函数在Server owned Actor上执行则只会在运行在服务器上不会传递到客户端。

View File

@@ -0,0 +1,52 @@
# NetMulticast
- **功能描述:** 定义一个多播RPC函数在服务器和客户端上都执行。对应的实现函数会添加_Implementation后缀。
- **元数据类型:** bool
- **引擎模块:** Network
- **作用机制:** 在FunctionFlags中加入[FUNC_Net](../../../../Flags/EFunctionFlags/FUNC_Net.md)、[FUNC_NetMulticast](../../../../Flags/EFunctionFlags/FUNC_NetMulticast.md)
- **常用程度:★★★★★**
定义一个多播RPC函数在服务器和客户端上都执行。对应的实现函数会添加_Implementation后缀。
RPC执行的规则参考文档[https://docs.unrealengine.com/4.27/zh-CN/InteractiveExperiences/Networking/Actors/RPCs/](https://docs.unrealengine.com/4.27/zh-CN/InteractiveExperiences/Networking/Actors/RPCs/)
![Untitled](Untitled.png)
## 测试代码:
```cpp
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API AMyFunction_Network :public AActor
{
public:
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, NetMulticast, Reliable)
void MyFunc_NetMulticast();
};
void AMyFunction_Network::MyFunc_NetMulticast_Implementation()
{
UInsiderLibrary::PrintFuncStatus(this, TEXT("MyFunc_NetMulticast_Implementation"));
}
```
测试蓝图PIE模式一个ListenServer+2Client
![Untitled](Untitled.png)
## 测试输出结果:
```cpp
LogInsider: Display: 46715a00 MyFunc_NetMulticast_Implementation BP_Network_C_1 NM_ListenServer Local:ROLE_Authority Remote:ROLE_SimulatedProxy
LogInsider: Display: 46e65000 MyFunc_NetMulticast_Implementation BP_Network_C_1 NM_Client Local:ROLE_SimulatedProxy Remote:ROLE_Authority
LogInsider: Display: 29aaaa00 MyFunc_NetMulticast_Implementation BP_Network_C_1 NM_Client Local:ROLE_SimulatedProxy Remote:ROLE_Authority
LogInsider: Display: 4ff44600 OtherMulticastFunc BP_Network_C_1 NM_ListenServer Local:ROLE_Authority Remote:ROLE_SimulatedProxy
LogInsider: Display: 3bf89b00 OtherMulticastFunc BP_Network_C_1 NM_Client Local:ROLE_SimulatedProxy Remote:ROLE_Authority
LogInsider: Display: 29d68700 OtherMulticastFunc BP_Network_C_1 NM_Client Local:ROLE_SimulatedProxy Remote:ROLE_Authority
```
在一个Server Owned的Actor上发出Multicast RPC事件调用可以见到在3个端都得到了调用。

View File

@@ -0,0 +1,12 @@
# Reliable
- **功能描述:** 指定一个RPC函数为“可靠的”当遇见网络错误时会重发以保证到达。一般用在逻辑关键的函数上。
- **元数据类型:** bool
- **引擎模块:** Network
- **作用机制:** 在FunctionFlags加入[FUNC_NetReliable](../../../Flags/EFunctionFlags/FUNC_NetReliable.md)
- **常用程度:★★★★★**
指定一个RPC函数为“可靠的”当遇见网络错误时会重发以保证到达。一般用在逻辑关键的函数上。
具体的原理涉及到了重发信息包的逻辑。

View File

@@ -0,0 +1,52 @@
# Server
- **功能描述:** 在Client-owned的Actor上PlayerController或Pawn执行一个RPC函数只运行在服务器上。对应的实现函数会添加_Implementation后缀
- **元数据类型:** bool
- **引擎模块:** Network
- **作用机制:** 在FunctionFlags中加入[FUNC_Net](../../../../Flags/EFunctionFlags/FUNC_Net.md)、[FUNC_NetServer](../../../../Flags/EFunctionFlags/FUNC_NetServer.md)
- **常用程度:★★★★★**
在Client-owned的Actor上PlayerController或Pawn执行一个RPC函数只运行在服务器上。对应的实现函数会添加_Implementation后缀。
和RunOnServer的效果一样。
所谓Client-owned参考文档[https://docs.unrealengine.com/4.27/zh-CN/InteractiveExperiences/Networking/Actors/RPCs/](https://docs.unrealengine.com/4.27/zh-CN/InteractiveExperiences/Networking/Actors/RPCs/)
![Untitled](Untitled.png)
## 测试代码:
```cpp
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API AMyFunction_PlayerController :public APlayerController
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Server, Reliable)
void MyFunc_RunOnServer();
};
void AMyFunction_PlayerController::MyFunc_RunOnServer_Implementation()
{
UInsiderLibrary::PrintFuncStatus(this, TEXT("MyFunc_RunOnServer_Implementation"));
}
```
测试蓝图PIE模式一个ListenServer+2Client
![Untitled](Untitled.png)
## 测试输出结果:
```cpp
LogInsider: Display: 5118b400 MyFunc_RunOnServer_Implementation BP_NetworkPC_C_1 NM_ListenServer Local:ROLE_Authority Remote:ROLE_AutonomousProxy
LogInsider: Display: 44ec3c00 MyFunc_RunOnServer_Implementation BP_NetworkPC_C_2 NM_ListenServer Local:ROLE_Authority Remote:ROLE_AutonomousProxy
LogInsider: Display: 49999000 OtherServerFunc BP_NetworkPC_C_1 NM_ListenServer Local:ROLE_Authority Remote:ROLE_AutonomousProxy
LogInsider: Display: 4bcbd800 OtherServerFunc BP_NetworkPC_C_2 NM_ListenServer Local:ROLE_Authority Remote:ROLE_AutonomousProxy
```
可见测试代码中取第2个PC发出一个Run on Server的RPC调用最终在Server上成功触发。C++定义的函数和蓝图中添加的自定义RunOnServer事件效果是等价的。
而如果这个函数在Server owned Actor上执行则只会在运行在服务器上不会传递到客户端。

View File

@@ -0,0 +1,39 @@
# ServiceRequest
- **功能描述:** 此函数为RPC远程过程调用服务请求。rpc服务请求
- **元数据类型:** bool
- **引擎模块:** Network
- **作用机制:** 在Meta中加入[CustomThunk](../../../Meta/UHT/CustomThunk.md)在FunctionFlags加入[FUNC_Net](../../../Flags/EFunctionFlags/FUNC_Net.md)、[FUNC_Event](../../../Flags/EFunctionFlags/FUNC_Event.md)、[FUNC_NetReliable](../../../Flags/EFunctionFlags/FUNC_NetReliable.md)、[FUNC_NetRequest](../../../Flags/EFunctionFlags/FUNC_NetRequest.md)
在源码里都没看到使用,只搜到
```cpp
UCLASS()
class UTestReplicationStateDescriptor_TestFunctionWithNotReplicatedNonPODParameters : public UObject
{
GENERATED_BODY()
protected:
// Currently some features such as not replicating all parameters isn't allowed on regular RPCs
UFUNCTION(ServiceRequest(Iris))
void FunctionWithNotReplicatedNonPODParameters(int Param0, bool Param1, int Param2, UPARAM(NotReplicated) const TArray<FTestReplicationStateDescriptor_TestStructWithRefCArray>& NotReplicatedParam3);
void FunctionWithNotReplicatedNonPODParameters_Implementation(int Param0, bool Param1, int Param2, UPARAM(NotReplicated) const TArray<FTestReplicationStateDescriptor_TestStructWithRefCArray>& NotReplicatedParam3);
};
```
## UDN回答
Alex: Those specifiers were added quite a while ago as a way to mark functions as RPC requests/responses to and from a backend service, the name of which would be given as part of the specifier: UFUNCTION(ServiceRequest(<Endpoint Name>)). However, the feature was never fully implemented, and since then the specifiers have only been used internally (and even then, I don't believe "ServiceResponse" is used at all anymore). This is why there isn't any public documentation or examples available, as they're not formally supported in the engine. You can check out ServiceRequestSpecifier and ServiceResponseSpecifier in UhtFunctionSpecifiers.cs to see how UHT handles these specifiers.
Mi: 这两个标记是我们用来自由扩展和自己的服务器通信的例如http request譬如可以提供自己的NetDriver处理特定标记的ServiceRequest的RPC自己序列化对应参数发给自己的服务。
“意思是如果使用引擎的默认实现的话使用这两个标记是无效的吗我尝试在服务器或者客户端发起对ServiceRequest标记的ufunction的调用结果都是会打印错误日志”
是的默认的UE client和DS通信的NetDriver的RPC不需要这两个关键字用了之后会找不到相应处理的NetDriver的实现。
在Server Owned Actor上调用会出错LogNet: Warning: UNetDriver::ProcessRemoteFunction: No owning connection for actor BP_Network_C_1. Function MyFunc_ServiceRequest will not be processed.
在PC上Server调用也会
LogRep: Error: Rejected RPC function due to access rights. Object: BP_NetworkPC_C /Game/UEDPIE_0_StartMap.StartMap:PersistentLevel.BP_NetworkPC_C_1, Function: MyFunc_ServiceRequest
LogNet: Error: UActorChannel::ProcessBunch: Replicator.ReceivedBunch failed. Closing connection. RepObj: BP_NetworkPC_C /Game/UEDPIE_0_StartMap.StartMap:PersistentLevel.BP_NetworkPC_C_1, Channel: 3

View File

@@ -0,0 +1,9 @@
# ServiceResponse
- **功能描述:** 此函数为RPC服务响应。rpc服务回复
- **元数据类型:** bool
- **引擎模块:** Network
- **作用机制:** 在FunctionFlags加入[FUNC_Net](../../../Flags/EFunctionFlags/FUNC_Net.md)、[FUNC_Event](../../../Flags/EFunctionFlags/FUNC_Event.md)、[FUNC_NetReliable](../../../Flags/EFunctionFlags/FUNC_NetReliable.md)、[FUNC_NetResponse](../../../Flags/EFunctionFlags/FUNC_NetResponse.md)
在源码里一个也没看到使用。

View File

@@ -0,0 +1,9 @@
# Unreliable
- **功能描述:** 指定一个RPC函数为“不可靠的”当遇见网络错误时就会被丢弃。一般用在传播效果表现的函数上就算漏掉也没有关系。
- **元数据类型:** bool
- **引擎模块:** Network
- **常用程度:★★★★★**
指定一个RPC函数为“不可靠的”当遇见网络错误时就会被丢弃。一般用在传播效果表现的函数上就算漏掉也没有关系。

View File

@@ -0,0 +1,118 @@
# WithValidation
- **功能描述:** 指定一个RPC函数在执行前需要验证只有验证通过才可以执行。
- **元数据类型:** bool
- **引擎模块:** Network
- **作用机制:** 在FunctionFlags中加入[FUNC_NetValidate](../../../Flags/EFunctionFlags/FUNC_NetValidate.md)
- **常用程度:★★★★★**
指定一个RPC函数在执行前需要验证只有验证通过才可以执行。
WithValidation实际上可以用于ClientServerNetMulticast的RPC函数但一般来说还是用在Server的最多因为一般是Server的数据最权威可以进行数据合法性校验。
## 测试代码:
```cpp
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API AMyFunction_PlayerController :public APlayerController
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Client, Reliable,WithValidation)
void MyFunc2_RunOnClient();
UFUNCTION(BlueprintCallable, Server, Reliable,WithValidation)
void MyFunc2_RunOnServer();
};
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API AMyFunction_Network :public AActor
{
public:
GENERATED_BODY()
UFUNCTION(BlueprintCallable, NetMulticast, Reliable,WithValidation)
void MyFunc2_NetMulticast();
};
void AMyFunction_PlayerController::MyFunc2_RunOnServer_Implementation()
{
UInsiderLibrary::PrintFuncStatus(this, TEXT("MyFunc2_RunOnServer_Implementation"));
}
bool AMyFunction_PlayerController::MyFunc2_RunOnServer_Validate()
{
UInsiderLibrary::PrintFuncStatus(this, TEXT("MyFunc2_RunOnServer_Validate"));
return true;
}
bool AMyFunction_Network::MyFunc2_NetMulticast_Validate()
{
UInsiderLibrary::PrintFuncStatus(this, TEXT("MyFunc2_NetMulticast_Validate"));
return true;
}
```
## 测试结果:
```cpp
RunOnClient:
LogInsider: Display: 815f7800 MyFunc2_RunOnClient_Validate BP_NetworkPC_C_0 NM_Client Local:ROLE_AutonomousProxy Remote:ROLE_Authority
LogInsider: Display: 815f7800 MyFunc2_RunOnClient_Implementation BP_NetworkPC_C_0 NM_Client Local:ROLE_AutonomousProxy Remote:ROLE_Authority
RunOnServer:
LogInsider: Display: 7fd11800 MyFunc2_RunOnServer_Validate BP_NetworkPC_C_1 NM_ListenServer Local:ROLE_Authority Remote:ROLE_AutonomousProxy
LogInsider: Display: 7fd11800 MyFunc2_RunOnServer_Implementation BP_NetworkPC_C_1 NM_ListenServer Local:ROLE_Authority Remote:ROLE_AutonomousProxy
Multicast: ServerOwned
LogInsider: Display: 947e6400 MyFunc2_NetMulticast_Validate BP_Network_C_1 NM_ListenServer Local:ROLE_Authority Remote:ROLE_SimulatedProxy
LogInsider: Display: 947e6400 MyFunc2_NetMulticast_Implementation BP_Network_C_1 NM_ListenServer Local:ROLE_Authority Remote:ROLE_SimulatedProxy
LogInsider: Display: 8795eb00 MyFunc2_NetMulticast_Validate BP_Network_C_1 NM_Client Local:ROLE_SimulatedProxy Remote:ROLE_Authority
LogInsider: Display: 8795eb00 MyFunc2_NetMulticast_Implementation BP_Network_C_1 NM_Client Local:ROLE_SimulatedProxy Remote:ROLE_Authority
LogInsider: Display: 8f6a3700 MyFunc2_NetMulticast_Validate BP_Network_C_1 NM_Client Local:ROLE_SimulatedProxy Remote:ROLE_Authority
LogInsider: Display: 8f6a3700 MyFunc2_NetMulticast_Implementation BP_Network_C_1 NM_Client Local:ROLE_SimulatedProxy Remote:ROLE_Authority
```
## 原理:
如果加上WithValidation标记在UHT生成代码的时候就会
```cpp
DEFINE_FUNCTION(AMyFunction_PlayerController::execMyFunc2_RunOnServer)
{
P_FINISH;
P_NATIVE_BEGIN;
if (!P_THIS->MyFunc2_RunOnServer_Validate())
{
RPC_ValidateFailed(TEXT("MyFunc2_RunOnServer_Validate"));
return;
}
P_THIS->MyFunc2_RunOnServer_Implementation();
P_NATIVE_END;
}
DEFINE_FUNCTION(AMyFunction_PlayerController::execMyFunc2_RunOnClient)
{
P_FINISH;
P_NATIVE_BEGIN;
if (!P_THIS->MyFunc2_RunOnClient_Validate())
{
RPC_ValidateFailed(TEXT("MyFunc2_RunOnClient_Validate"));
return;
}
P_THIS->MyFunc2_RunOnClient_Implementation();
P_NATIVE_END;
}
DEFINE_FUNCTION(AMyFunction_Network::execMyFunc2_NetMulticast)
{
P_FINISH;
P_NATIVE_BEGIN;
if (!P_THIS->MyFunc2_NetMulticast_Validate())
{
RPC_ValidateFailed(TEXT("MyFunc2_NetMulticast_Validate"));
return;
}
P_THIS->MyFunc2_NetMulticast_Implementation();
P_NATIVE_END;
}
```