-
Notifications
You must be signed in to change notification settings - Fork 0
/
ECS.cs
198 lines (174 loc) · 6.9 KB
/
ECS.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
using EntityComponentSystem.Cache;
using EntityComponentSystem.Systems;
using Sandbox;
using Sandbox.Diagnostics;
using System;
using System.Collections.Generic;
using System.Linq;
namespace EntityComponentSystem;
/// <summary>
/// A container class for handling all ECS systems. Implemented as a non-static class so extension methods can exist.
/// </summary>
public sealed class ECS
{
/// <summary>
/// The only instance of <see cref="ECS"/> in existence.
/// </summary>
public static ECS? Instance { get; private set; }
/// <summary>
/// The logger for all ECS related logging.
/// </summary>
internal static Logger? Logger { get; private set; }
/// <summary>
/// A publically facing cache interface for system queries.
/// </summary>
public IQueryCache Cache => cache;
/// <summary>
/// A cache for system queries.
/// </summary>
private readonly IInternalQueryCache cache;
/// <summary>
/// The configuration applied to the ECS world.
/// </summary>
internal readonly ECSConfiguration Configuration;
/// <summary>
/// A reference to the manager of the game that is being played.
/// </summary>
private readonly BaseGameManager? gameManager;
/// <summary>
/// Initializes a default instance of <see cref="ECS"/>.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when no <see cref="BaseGameManager"/> was found.</exception>
public ECS() : this( ECSConfiguration.Default ) { }
/// <summary>
/// Initializes an instance of <see cref="ECS"/> with a custom configuration.
/// </summary>
/// <param name="configuration">The custom configuration to apply to the ECS world.</param>
/// <exception cref="InvalidOperationException">Thrown when no <see cref="BaseGameManager"/> was found.</exception>
public ECS( ECSConfiguration configuration )
{
Instance = this;
// Clone the configuration.
this.Configuration = new( configuration );
// FIXME: Libraries don't have base referenced so this is the only way to get the game manager.
gameManager = Entity.All.OfType<BaseGameManager>().FirstOrDefault();
if ( gameManager is null )
throw new InvalidOperationException( "No game manager found, did you create this too early?" );
// Create cache.
cache = configuration.UseCaching ? new QueryCache() : new FakeQueryCache();
// Create logger.
Logger = new Logger( "ECS" );
}
/// <summary>
/// Adds a new system to the world.
/// </summary>
/// <typeparam name="TSystem">The type of system to add.</typeparam>
/// <typeparam name="TEntity">The type of entity that the system works with.</typeparam>
/// <exception cref="ArgumentException">Thrown when an instance of the system already exists.</exception>
public void AddSystem<TSystem, TEntity>()
where TSystem : ISystem<TEntity>, new()
where TEntity : IEntity
{
if ( gameManager!.Components.Get<TSystem>() is not null )
throw new ArgumentException( $"A system of type {typeof( TSystem ).Name} already exists", nameof( TSystem ) );
gameManager!.Components.Add( new TSystem() );
if ( Configuration.IsLoggerEnabled( Logs.SystemAdded ) )
Logger!.Info( $"System \"{typeof( TSystem ).Name}\" added" );
}
/// <summary>
/// Adds a new system to the world.
/// </summary>
/// <typeparam name="TSystem">The type of system to add.</typeparam>
public void AddSystem<TSystem>()
where TSystem : ISystem<IEntity>, new()
{
AddSystem<TSystem, IEntity>();
}
/// <summary>
/// Adds an existing system to the world.
/// </summary>
/// <typeparam name="TEntity">The type of entity that the system works with.</typeparam>
/// <param name="system">The system to add.</param>
public void AddSystem<TEntity>( ISystem<TEntity> system )
where TEntity : IEntity
{
gameManager!.Components.Add( system );
if ( Configuration.IsLoggerEnabled( Logs.SystemAdded ) )
Logger!.Info( $"System \"{system.GetType().Name}\" added" );
}
/// <summary>
/// Executes systems with a sequence of entities.
/// </summary>
/// <typeparam name="TSystem">The type of systems to execute.</typeparam>
/// <typeparam name="TEntity">The type of entities to execute on.</typeparam>
/// <param name="entities">The sequence of entities to execute on.</param>
/// <param name="args">The arguments to pass to the system executor.</param>
public void Run<TSystem, TEntity>( IEnumerable<TEntity> entities, params object[] args )
where TSystem : ISystem<TEntity>
where TEntity : IEntity
{
RunInternal<TSystem, TEntity>( entities, args );
}
/// <summary>
/// Executes systems with all entities in S&box that match <see ref="TEntity"/>.
/// </summary>
/// <typeparam name="TSystem">The type of systems to execute.</typeparam>
/// <typeparam name="TEntity">The type of entities to execute on.</typeparam>
/// <param name="args">The arguments to pass to the system executor.</param>
public void Run<TSystem, TEntity>( params object[] args )
where TSystem : ISystem<TEntity>
where TEntity : IEntity
{
RunInternal<TSystem, TEntity>( Entity.All.OfType<TEntity>(), args );
}
/// <summary>
/// Executes systems with all entities in S&box.
/// </summary>
/// <typeparam name="TSystem">The type of systems to execute.</typeparam>
/// <param name="args">The arguments to pass to the system executor.</param>
public void Run<TSystem>( params object[] args )
where TSystem : ISystem<IEntity>
{
RunInternal<TSystem, IEntity>( Entity.All, args );
}
/// <summary>
/// Executes systems with a sequence of entities.
/// </summary>
/// <typeparam name="TEntity">The type of entities to execute on.</typeparam>
/// <param name="entities">The sequence of entities to execute on.</param>
/// <param name="args">The arguments to pass to the system executor.</param>
public void Run<TEntity>( IEnumerable<TEntity> entities, params object[] args )
where TEntity : IEntity
{
RunInternal<ISystem<TEntity>, TEntity>( entities, args );
}
/// <summary>
/// Executes systems with all entities in S&box.
/// </summary>
/// <param name="args">The arguments to pass to the system executor.</param>
public void Run( params object[] args )
{
RunInternal<ISystem<IEntity>, IEntity>( Entity.All, args );
}
/// <summary>
/// The internal method to executing systems on a sequence of entities.
/// </summary>
/// <typeparam name="TSystem">The type of systems to execute.</typeparam>
/// <typeparam name="TEntity">The type of entities to execute on.</typeparam>
/// <param name="entities">The sequence of entities to execute on.</param>
/// <param name="args">The arguments to pass to the system executor.</param>
private void RunInternal<TSystem, TEntity>( IEnumerable<TEntity> entities, object[] args )
where TSystem : ISystem<TEntity>
where TEntity : IEntity
{
foreach ( var component in gameManager!.Components.GetAll<TSystem>() )
{
if ( component is IServerSystem && !Game.IsServer )
continue;
if ( component is IClientSystem && !Game.IsClient )
continue;
var query = cache.GetOrCache( component, entities );
component.Execute( query, args );
}
}
}