# LockstepECS
**Repository Path**: alex67/LockstepECS
## Basic Information
- **Project Name**: LockstepECS
- **Description**: Fast Lockstep ECS Framework
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2019-12-04
- **Last Updated**: 2020-12-19
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# LockstepECS
## **简介**
LockstepECS 一个基于c# 指针和结构体 的帧同步框架,使用于超大型场景的帧同步游戏
优点:
- 运行速度快,使用指针,和结构体,基本无gc , PureMode 都比Entitas 快两倍,Burst Mode,快四倍以上
- 内存紧凑,预测回滚是否帧状态拷贝快 7000 只鱼的状态拷贝只消耗0.3ms
- API 和 UNITY ECS 非常相似,可以使用同一种编程范式来编写 logic 层 和 view 层
- 无缝兼容UnityECS ,使用条件宏可以切换两种模式,
- PureMode:纯代码形式,可以直接在服务器中运行逻辑,不依赖Unity
- Burst Mode: 模式,直接生成适配Unity ECS Burst+job框架代码的代码,进一步提升运行速度
- github 上的是免费版
## **Reference**
- [帧同步基础库 https://github.com/JiepengTan/LockstepEngine][1]
- [高性能帧同步ECS框架 https://github.com/JiepengTan/LockstepECS][8]
- [代码生成DSL https://github.com/JiepengTan/ME][2]
- [demo: 帧同步版联机版 模拟鲨鱼围捕 2000 条小鱼 https://github.com/JiepengTan/LcokstepECS_Demo_Boid][3]
- [帧同步教程 https://github.com/JiepengTan/Lockstep-Tutorial][4]
- **LockstepECS 建议或bug 群**
928084598,功能仅限提bug和建议
- **Unity DOTS 技术交流 微信群**

```
为了过滤伸手党,不浪费群里人时间,有ECS使用经验后你再加(能独立用ECS写个小游戏)
添加微信请备注 LECS: + 你的github网址
eg:我个人GitHub 地址为 https://github.com/JiepengTan
备注应该填写为:LECS:JiepengTan
```
## **视频链接**
- [环境搭建(Win & Mac)][9]
- [Boid Demo 运行 ][10]
## **TODO**
- 预测回滚
- 碰撞检测库
- 寻路库
- 序列化库向前兼容
## **1.安装运行**
### **1.ClientMode**
- 1.打开场景

- 2. 确保钩上了 ClientMode 选项

- 3. 运行 游戏
A D 控制方向
Space 释放技能 (Boid demo 才有技能)
### **2.联网模式**
- 1.打开Game.sln

- 2.编译并运行 Game.sln
![[图片上传中...(Screen Shot 2019-11-13 at 4.50.45 PM.png-75d531-1573782602414-0)]
](https://upload-images.jianshu.io/upload_images/11593954-33601348642f30af.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 3.确保关闭了单机模式 ClientMode

- 4.Build

- 5.Run Client

- 6. 现在是简单帧同步模式

(Build 版本代码中为了方便测试,做了限制自动绕圈圈,你可以修改他)

## **2. 开发**
### 1. 目录安排

- 1. Game.Model
这里放置的是MVC 中Model and Control 代码(Logic 层)
- 2. Game.View
这里放置的是MVC 中View 代码(View 层),依赖于Model 层
- 3. Tools.UnsafeECS.ECSDefine.Game
这里放置的是你对游戏中 Entity Component System 以及全局状态 State 的定义,必须保证该子dll 是能编译的(不能依赖于Model 或 View 层)
### 2. 环境要求
- 1.需要安装NetCore2.2以上
- 2.需要安装Mono
- 3.GitBash
### 3. Tools.UnsafeECS.ECSDefine 定义详解
#### 0. 代码生成
1. Windows 用户
- 使用gitBash 执行 Client.Unity/DataAndTools/Tools/UpdateAndCodeGen_Win.sh
2. Mac 用户
- 使用命令行执行 Client.Unity/DataAndTools/Tools/UpdateAndCodeGen.sh
- 或直接双击运行 Client.Unity/DataAndTools/Tools/UpdateAndCodeGen (注意无后缀)
#### 1. 接口定义
```cpp
// Copyright 2019 谭杰鹏. All Rights Reserved //https://github.com/JiepengTan
/// should declare a Method like:
/// public void Execute(.....)
public interface ISystem { }
/// should declare a Method like:
/// public void Execute(int index, .....)
public interface ISystemWithIdx { }
/// should declare a Method like:
/// public void Execute(Entity* ptr, .....)
public interface ISystemWithEntity { }
/// this system would always be scheduled in main thread
/// should declare a Method like:
/// public void Execute(.....)
public interface IPureSystem : ISystem { }
/// this system would always be scheduled in main thread
/// should declare a Method like:
/// public void Execute(Entity* ptr, .....)
public interface IPureSystemWithEntity : IPureSystem, ISystemWithEntity { }
/// If macro UNING_UNITY_BURST_JOB was defined
/// this system will be complied by Unity's Burst compiler and scheduled by Unity's JobSystem,Otherwise it will be scheduled call in MainThread
/// should declare a Method like:
/// public void Execute(.....)
public interface IJobSystem : ISystem { }
/// If macro UNING_UNITY_BURST_JOB was defined
/// this system will be complied by Unity's Burst compiler and scheduled by Unity's JobSystem,Otherwise it will be scheduled call in MainThread
/// should declare a Method like:
/// public void Execute(Entity* ptr, .....)
public interface IJobSystemWithEntity : IJobSystem, ISystemWithEntity { }
/// If macro UNING_UNITY_BURST_JOB was defined
/// this system will be complied by Unity's Burst compiler and scheduled by Unity's JobSystem,Otherwise it will be scheduled call in MainThread
/// should declare a Method like:
/// public void Execute(int index, .....)
public interface IJobForEachSystem : IJobSystem { }
/// If macro UNING_UNITY_BURST_JOB was defined
/// this system will be complied by Unity's Burst compiler and scheduled by Unity's JobSystem,Otherwise it will be scheduled call in MainThread
/// should declare a Method like:
/// public void Execute(Entity* ptr,int index, .....)
public interface IJobForEachSystemWithEntity : IJobForEachSystem, ISystemWithEntity { }
/// If macro UNING_UNITY_BURST_JOB was defined
/// this system will be complied by Unity's Burst compiler and scheduled by Unity's JobSystem,Otherwise it will be scheduled call in MainThread
/// should declare as if it was inherited from : Unity.Collections.IJobNativeMultiHashMapMergedSharedKeyIndices
public interface IJobHashMapSystem : ISystem { }
/// Component in game
public interface IGameComponent : IComponent { }
/// Service using in game
public interface IGameService{}
/// Game status which need read from config
public interface IGameConfigService{}
/// Game status which can not be modified in game
public interface IGameConstStateService{}
/// Game status can changed during game
public interface IGameStateService{}
/// Component
public interface IComponent { }
/// Entity
public interface IEntity { }
/// Game status
public interface IServiceState { }
/// Game Events
public interface IEvent{}
/// CollisionEvent define (TODO)
public interface ICollisionEvent { }
/// Create a game object to bind Entity
/// to view the Entity's status or Attach some effect to the gameObject
/// reference to :CodeGen_EntityView.cs
public interface IBindViewEntity{}
/// synchronize Unsafe Entity's Position and Rotation to Unity Entity
/// reference to :CodeGen_UpdateViewStateSystem.cs
public interface IUpdateViewEntity{}
````
#### 2. 属性集合
- 1. EntityCountAttribute
定义默认初始化的保留的Entity 数量,用于优化 类似List 中的 capacity 中的概念
```
[EntityCount(1000)]
public partial class BoidCell : IEntity{
public CellData Cell;
}
```
- 1. NoGenCodeAttribute
提示代码生成器在代码生成的过程中忽略掉本类型
```
[NoGenCode] public class ConfigTargetInfo{}
```
#### 3. Componet 定义
1. Component 中可以使用的类型
```
bool
float //注意不支持double
byte
sbyte
short
ushort
int
uint
long
ulong
Vector2
Vector3
Quaternion
Vector2Int
Vctor3Int
EntityRef
Entity
```
2. 注意事项,应该使用Public 定义属性,同时需要最终继承自IGameComponent
```
public partial class BoidState : IGameComponent {
public float SinkTimer;
public bool IsDied;
public bool IsScored;
public int KillerIdx;
}
```
3. 可以使用继承
- Component define
```
public partial class TestCompBase1 : IGameComponent {
public int Count;
}
public partial class TestCompChild2 : TestCompBase1 {
public float Radius;
}
```
- Generated codes
```
[StructLayoutAttribute(LayoutKind.Sequential)]
[System.Serializable]
public unsafe partial struct TestCompBase1 {
public int Count;
public override Int32 GetHashCode() {
unchecked {
var hash = 7;
hash = hash * 37 +Count.GetHashCode();
return hash;
}
}
}
[StructLayoutAttribute(LayoutKind.Sequential)]
[System.Serializable]
public unsafe partial struct TestCompChild2 {
public LFloat Radius;
public int Count;
public override Int32 GetHashCode() {
unchecked {
var hash = 7;
hash = hash * 37 +Radius.GetHashCode();
hash = hash * 37 +Count.GetHashCode();
return hash;
}
}
}
```
#### 4. Entity 定义
1. 注意事项,应该使用Public 定义属性,同时需要最终继承自IEntity
2. 可以使用继承
- Define
```
[EntityCount(20)]
public partial class TestEntity1 : IEntity{
public CellData Cell;
}
[EntityCount(100)]
public partial class TestEntity2 : TestEntity1{
public SpawnData Spawn;
}
```
- Generated Codes
```
[StructLayoutAttribute(LayoutKind.Sequential)]
[System.Serializable]
public unsafe partial struct TestEntity1 :IEntity {
public const Int32 INIT_COUNT = 20;
internal Entity _entity;
// Fields
public CellData Cell;
// BuildIn properties
public EntityRef EntityRef =>_entity._ref;
public int EntityIndex =>_entity._ref._index;
public EEntityType EntityType => (EEntityType)(_entity._ref._type);
public bool IsActive =>_entity._active;
}
[StructLayoutAttribute(LayoutKind.Sequential)]
[System.Serializable]
public unsafe partial struct TestEntity2 :IEntity {
public const Int32 INIT_COUNT = 100;
internal Entity _entity;
// Fields
public SpawnData Spawn;
public CellData Cell;
// BuildIn properties
public EntityRef EntityRef =>_entity._ref;
public int EntityIndex =>_entity._ref._index;
public EEntityType EntityType => (EEntityType)(_entity._ref._type);
public bool IsActive =>_entity._active;
}
```
3. IUpdateViewEntity
如果需要将UnsafeECS 中的Entity 的位置于旋转属性同步到Unity 中的Entity ,需要继承自IUpdateViewEntity接口,参考代码 CodeGen_UpdateViewStateSystem.cs
4. IBindViewEntity
如果希望能够以GameObject 的形式 绑定相关的Entity,这样方便挂接特效等一些View 层的脚本,可以继承自IBindViewEntity 接口,参考代码 CodeGen_EntityView.cs

#### 5. System
1. **Overview**
- System Define
- IJobSystem
- IPureSystem
- System Implement
- GameJobSystem
- GameExecuteSystem
- **其中以Job开头的定义,在UNING_UNITY_BURST_JOB 定义了的情况下,会使用Unity 的Burst 进行编译,且会交由Unity 的JobSystem 进行调度,否则会在主线程中调度,所以这类System,不能修改全局的状态,只能访问相关的属性**
- **而 IPureSystem 一定会被主线程调用,适合处理涉及全局状态的改变的任务**
```
public abstract unsafe class BaseExecuteSystem : BaseSystem, IExecuteSystem {
public void Execute(IContext context){
if (BeforeSchedule()) { DoSchedule(context); }
AfterSchedule(isSucc);
}
protected virtual bool BeforeSchedule(){ return true;}
protected virtual void DoSchedule(IContext context){ context.Schedule(this); }
protected virtual void AfterSchedule(bool isSucc){ }
}
public partial class GameExecuteSystem :BaseExecuteSystem{ }
```
- 你可以Override BeforeSchedule 在正式调度前去设置一下状态,方便在Execute 中访问
- 你可以Override AfterSchedule 在调度完成后去清理一下状态
2. **PureSystem**
这类System 继承自IPureSystem 如:
- IPureSystem
- IPureSystemWithEntity
这类System 中的Execute 方法必定是在主线程中调用,一旦涉及到全局状态的修改,就必须使用这种类型的System , 比如 Entity 的创建以及销毁,全局状态的设置,如demo中的SpawnSystem
- System Define
```
public class SpawnSystem : IPureSystemWithEntity {
public SpawnData SpawnData;
public AssetData AssetData;
}
```
- System Implement
```
public unsafe partial class SpawnSystem : GameExecuteSystem {
public void Execute(Entity* entity, ref SpawnData spawnData, ref AssetData assetData){
var count = spawnData.Count;
var center = spawnData.Position;
var radius = spawnData.Radius;
var spawnPositions = new NativeArray(count, Allocator.Temp,
NativeArrayOptions.UninitializedMemory);
GeneratePoints.RandomPointsInUnitCube(spawnPositions);
var pointPtr = spawnPositions.GetPointer(0);
var context = _context;
for (int i = 0; i < count; ++i, ++pointPtr) {
var boidPtr = context.PostCmdCreateBoid();
boidPtr->Transform.Position = center + (*pointPtr * radius);
boidPtr->Transform.Forward = *pointPtr;
boidPtr->Transform.Scale = 1;
boidPtr->Prefab.AssetId = assetData.AssetId;
}
spawnPositions.Dispose();
context.PostCmdDestroyEntity(entity);
}
}
```
3. **JobSystem**
这类System 继承自IJobSystem 如:
- IJobSystem
- IJobSystemWithEntity
- IJobForEachSystem
- IJobForEachSystemWithEntity
- IJobHashMapSystem
If macro UNING_UNITY_BURST_JOB was defined
/// this system will be complied by Unity's Burst compiler and scheduled by Unity's JobSystem,Otherwise it will be scheduled call in MainThread
- System Define
```
public class SinkSystem : IJobSystem {
public Transform3D Transform;
public BoidState BoidState;
}
```
- System Implement
```
public unsafe partial class SinkSystem : GameJobSystem {
public unsafe partial struct JobDefine {
[ReadOnly] public LVector3 SinkOffset;
[ReadOnly] public LFloat DeltaTime;
public void Execute(ref Transform3D transform3D, ref BoidState boidState){
if (!boidState.IsDied) return;
boidState.SinkTimer -= DeltaTime;
transform3D.Position += SinkOffset;
}
}
protected override bool BeforeSchedule(){
//assign jobData info
JobData.DeltaTime = _globalStateService.DeltaTime;
JobData.SinkOffset = new LVector3(0,_gameConfigService.BoidSettting.SinkSpd * JobData.DeltaTime,0) ;
return true;
}
}
```
#### 6. Status & Config
- 你需要在 GameStateService 定义游戏中会使用到的可以变化的变量
框架会帮你自动进行状态的备份以及还原
```
public partial class GameStateService : IServiceState,IGameStateService {
// states
public bool IsPlaying;
public bool IsGameOver;
public byte LocalEntityId;
// volatile states
public int CurEnemyCount;
public int CurScore;
public float CurScale;
}
```
- 如果你的变量是配置,在游戏加载后就不再变动,可以放置在GameConfigService 中,框架会自动帮你生成相关的ScriptObject 方便进行配置(其中使用到的类型不需要在 ECDefine 中定义,只需要声明有这样的一个类型,并且可以编译通过即可,可以参考 Tools.UnsafeECS.ECDefine.Game/Src/Unsafe/Status.cs)
```
public partial class GameConfigService : IServiceState,IGameConfigService {
public string RelPath;
public string RecorderFilePath;
public string DumpStrPath;
public int InitScale;
public int MaxPlayerCount;
public CollisionConfig CollisionConfig;
public Msg_G2C_GameStartInfo ClientModeInfo;
public List TargetInfos;
public List SpawnInfos;
public List ObstacleInfos;
public ConfigBoidSharedData BoidSettting;
}
```
#### 7. Events
```
public class OnSkillFire: IEvent{
public SkillData SkillData;
}
public class OnSkillDone: IEvent{
public SkillData SkillData;
}
```
框架会自动生成相关的Service
自需要在相关的Service 中实现相应的事件的响应,或者二次派发亦可,
```
public unsafe partial interface IGameEventService : IService{
void OnSkillFire(Entity* ptr
,ref SkillData SkillData
);
void OnSkillDone(Entity* ptr
,ref SkillData SkillData
);
}
```
可以在相应的GameExecuteSystem 中调用 RaiseEventXxxx 事件
如:
```
public unsafe partial class SkillSystem : GameExecuteSystem {
private LFloat _deltaTime;
public void Execute(Entity* ptr,ref SkillData skillData){
//skillData.IsFiring = skillMonoData.IsFiring;
if (skillData.IsNeedFire) {
if (skillData.CdTimer <= 0 && !skillData.IsFiring) {
skillData.CdTimer = skillData.CD;
skillData.IsFiring = true;
skillData.DurationTimer = skillData.Duration;
RaiseEventOnSkillFire(ptr,ref skillData);
}
}
skillData.CdTimer -= _deltaTime;
if (skillData.IsFiring) {
skillData.DurationTimer -= _deltaTime;
if (skillData.DurationTimer <= 0) {
skillData.IsFiring = false;
RaiseEventOnSkillDone(ptr,ref skillData);
}
}
skillData.IsNeedFire = false;
}
...
```
By convention, You should not call any method which start with **_**,eg: _DoDestroyEntity
[1]: https://github.com/JiepengTan/LockstepEngine
[2]: https://github.com/JiepengTan/ME
[3]: https://github.com/JiepengTan/LcokstepECS_Demo_Boid
[4]: https://github.com/JiepengTan/Lockstep-Tutorial
[5]: https://github.com/JiepengTan/FishManShaderTutorial
[6]: https://www.bilibili.com/video/av67829097
[7]: https://www.bilibili.com/video/av68850334
[8]: https://github.com/JiepengTan/LockstepECS
[9]: https://www.bilibili.com/video/av76298196
[10]: https://www.bilibili.com/video/av76311418