├── .github
└── workflows
│ ├── build.yml
│ └── publish.yml
├── .gitignore
├── .vscode
└── tasks.json
├── COPYING
├── Directory.Build.props
├── README.md
├── ReleaseNotes.md
├── Simplecs.sln
├── Simplecs
├── Containers
│ ├── ChunkedStorage.cs
│ ├── CircularBuffer.cs
│ ├── ComponentTable.cs
│ └── EntityAllocator.cs
├── Entity.cs
├── EntityBuilder.cs
├── Properties
│ └── AssemblyInfo.cs
├── Simplecs.csproj
├── ViewBuilder.cs
├── Views
│ ├── EntityEnumerator.cs
│ ├── View.cs
│ ├── ViewEnumerator.cs
│ ├── ViewPredicate.cs
│ └── ViewRow.cs
└── World.cs
├── SimplecsTests
├── ComponentTableTest.cs
├── Components.cs
├── EntityAllocatorTest.cs
├── SimplecsTests.csproj
├── ViewTest.cs
└── WorldTest.cs
└── omnisharp.json
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | pull_request: []
5 | push:
6 | branches:
7 | - master
8 |
9 | jobs:
10 | build:
11 | name: Build
12 | runs-on: ubuntu-16.04
13 |
14 | strategy:
15 | matrix:
16 | dotnet: [ '3.0.100' ]
17 | config: [ Debug, Release ]
18 |
19 | steps:
20 | - uses: actions/checkout@master
21 | - name: Setup dotnet
22 | uses: actions/setup-dotnet@v1
23 | with:
24 | dotnet-version: ${{ matrix.dotnet }}
25 | - run: dotnet restore
26 | name: Restore
27 | - run: dotnet build -c ${{ matrix.config }}
28 | name: Build
29 | - run: dotnet test -c ${{ matrix.config }}
30 | name: Test
31 | - run: dotnet pack -c ${{ matrix.config }} --include-symbols --version-suffix ci-${{ matrix.config }}-${{ github.sha }}
32 | name: Package
33 | - uses: actions/upload-artifact@master
34 | with:
35 | name: ci-packages
36 | path: pkg/Simplecs
37 | name: Artifacts
38 |
39 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*.*.*'
7 |
8 | jobs:
9 | build:
10 | name: Build
11 | runs-on: ubuntu-16.04
12 |
13 | steps:
14 | - uses: actions/checkout@master
15 | - name: Setup dotnet
16 | uses: actions/setup-dotnet@v1
17 | with:
18 | dotnet-version: 3.0.100
19 | - run: dotnet restore
20 | name: Restore
21 | - run: dotnet build -c Release
22 | name: Build
23 | - run: dotnet test -c Release
24 | name: Test
25 | - run: dotnet pack -c Release --include-symbols --include-source
26 | name: Package
27 | - uses: actions/upload-artifact@master
28 | with:
29 | name: packages
30 | path: pkg/Simplecs
31 | name: Artifacts
32 | - name: Release to GitHub
33 | uses: softprops/action-gh-release@v1
34 | with:
35 | draft: true
36 | body_path: ReleaseNotes.md
37 | files: pkg/Simplecs
38 | env:
39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
40 | - run: dotnet nuget push pkg/Simplecs/Simplecs.*.nupkg -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_KEY }}
41 | name: Publish to NuGet
42 |
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bin
2 | obj
3 | pkg
4 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "label": "build",
8 | "command": "dotnet",
9 | "type": "shell",
10 | "args": [
11 | "build",
12 | // Ask dotnet build to generate full paths for file names.
13 | "/property:GenerateFullPaths=true",
14 | // Do not generate summary otherwise it leads to duplicate errors in Problems panel
15 | "/consoleloggerparameters:NoSummary"
16 | ],
17 | "group": "build",
18 | "presentation": {
19 | "reveal": "silent"
20 | },
21 | "problemMatcher": "$msCompile"
22 | },
23 | {
24 | "label": "test",
25 | "command": "dotnet",
26 | "type": "shell",
27 | "args": [
28 | "test",
29 | // Ask dotnet build to generate full paths for file names.
30 | "/property:GenerateFullPaths=true",
31 | // Do not generate summary otherwise it leads to duplicate errors in Problems panel
32 | "/consoleloggerparameters:NoSummary"
33 | ],
34 | "dependsOn": [
35 | "build"
36 | ],
37 | "group": "test",
38 | "presentation": {
39 | "reveal": "always"
40 | },
41 | "problemMatcher": "$msCompile"
42 | }
43 | ]
44 | }
--------------------------------------------------------------------------------
/COPYING:
--------------------------------------------------------------------------------
1 | Creative Commons Legal Code
2 |
3 | CC0 1.0 Universal
4 |
5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
12 | HEREUNDER.
13 |
14 | Statement of Purpose
15 |
16 | The laws of most jurisdictions throughout the world automatically confer
17 | exclusive Copyright and Related Rights (defined below) upon the creator
18 | and subsequent owner(s) (each and all, an "owner") of an original work of
19 | authorship and/or a database (each, a "Work").
20 |
21 | Certain owners wish to permanently relinquish those rights to a Work for
22 | the purpose of contributing to a commons of creative, cultural and
23 | scientific works ("Commons") that the public can reliably and without fear
24 | of later claims of infringement build upon, modify, incorporate in other
25 | works, reuse and redistribute as freely as possible in any form whatsoever
26 | and for any purposes, including without limitation commercial purposes.
27 | These owners may contribute to the Commons to promote the ideal of a free
28 | culture and the further production of creative, cultural and scientific
29 | works, or to gain reputation or greater distribution for their Work in
30 | part through the use and efforts of others.
31 |
32 | For these and/or other purposes and motivations, and without any
33 | expectation of additional consideration or compensation, the person
34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she
35 | is an owner of Copyright and Related Rights in the Work, voluntarily
36 | elects to apply CC0 to the Work and publicly distribute the Work under its
37 | terms, with knowledge of his or her Copyright and Related Rights in the
38 | Work and the meaning and intended legal effect of CC0 on those rights.
39 |
40 | 1. Copyright and Related Rights. A Work made available under CC0 may be
41 | protected by copyright and related or neighboring rights ("Copyright and
42 | Related Rights"). Copyright and Related Rights include, but are not
43 | limited to, the following:
44 |
45 | i. the right to reproduce, adapt, distribute, perform, display,
46 | communicate, and translate a Work;
47 | ii. moral rights retained by the original author(s) and/or performer(s);
48 | iii. publicity and privacy rights pertaining to a person's image or
49 | likeness depicted in a Work;
50 | iv. rights protecting against unfair competition in regards to a Work,
51 | subject to the limitations in paragraph 4(a), below;
52 | v. rights protecting the extraction, dissemination, use and reuse of data
53 | in a Work;
54 | vi. database rights (such as those arising under Directive 96/9/EC of the
55 | European Parliament and of the Council of 11 March 1996 on the legal
56 | protection of databases, and under any national implementation
57 | thereof, including any amended or successor version of such
58 | directive); and
59 | vii. other similar, equivalent or corresponding rights throughout the
60 | world based on applicable law or treaty, and any national
61 | implementations thereof.
62 |
63 | 2. Waiver. To the greatest extent permitted by, but not in contravention
64 | of, applicable law, Affirmer hereby overtly, fully, permanently,
65 | irrevocably and unconditionally waives, abandons, and surrenders all of
66 | Affirmer's Copyright and Related Rights and associated claims and causes
67 | of action, whether now known or unknown (including existing as well as
68 | future claims and causes of action), in the Work (i) in all territories
69 | worldwide, (ii) for the maximum duration provided by applicable law or
70 | treaty (including future time extensions), (iii) in any current or future
71 | medium and for any number of copies, and (iv) for any purpose whatsoever,
72 | including without limitation commercial, advertising or promotional
73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
74 | member of the public at large and to the detriment of Affirmer's heirs and
75 | successors, fully intending that such Waiver shall not be subject to
76 | revocation, rescission, cancellation, termination, or any other legal or
77 | equitable action to disrupt the quiet enjoyment of the Work by the public
78 | as contemplated by Affirmer's express Statement of Purpose.
79 |
80 | 3. Public License Fallback. Should any part of the Waiver for any reason
81 | be judged legally invalid or ineffective under applicable law, then the
82 | Waiver shall be preserved to the maximum extent permitted taking into
83 | account Affirmer's express Statement of Purpose. In addition, to the
84 | extent the Waiver is so judged Affirmer hereby grants to each affected
85 | person a royalty-free, non transferable, non sublicensable, non exclusive,
86 | irrevocable and unconditional license to exercise Affirmer's Copyright and
87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the
88 | maximum duration provided by applicable law or treaty (including future
89 | time extensions), (iii) in any current or future medium and for any number
90 | of copies, and (iv) for any purpose whatsoever, including without
91 | limitation commercial, advertising or promotional purposes (the
92 | "License"). The License shall be deemed effective as of the date CC0 was
93 | applied by Affirmer to the Work. Should any part of the License for any
94 | reason be judged legally invalid or ineffective under applicable law, such
95 | partial invalidity or ineffectiveness shall not invalidate the remainder
96 | of the License, and in such case Affirmer hereby affirms that he or she
97 | will not (i) exercise any of his or her remaining Copyright and Related
98 | Rights in the Work or (ii) assert any associated claims and causes of
99 | action with respect to the Work, in either case contrary to Affirmer's
100 | express Statement of Purpose.
101 |
102 | 4. Limitations and Disclaimers.
103 |
104 | a. No trademark or patent rights held by Affirmer are waived, abandoned,
105 | surrendered, licensed or otherwise affected by this document.
106 | b. Affirmer offers the Work as-is and makes no representations or
107 | warranties of any kind concerning the Work, express, implied,
108 | statutory or otherwise, including without limitation warranties of
109 | title, merchantability, fitness for a particular purpose, non
110 | infringement, or the absence of latent or other defects, accuracy, or
111 | the present or absence of errors, whether or not discoverable, all to
112 | the greatest extent permissible under applicable law.
113 | c. Affirmer disclaims responsibility for clearing rights of other persons
114 | that may apply to the Work or any use thereof, including without
115 | limitation any person's Copyright and Related Rights in the Work.
116 | Further, Affirmer disclaims responsibility for obtaining any necessary
117 | consents, permissions or other rights required for any use of the
118 | Work.
119 | d. Affirmer understands and acknowledges that Creative Commons is not a
120 | party to this document and has no duty or obligation with respect to
121 | this CC0 or use of the Work.
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(SolutionDir)\bin\$(MSBuildProjectName)
5 | $(SolutionDir)\obj\$(MSBuildProjectName)
6 | $(SolutionDir)\pkg\$(MSBuildProjectName)
7 |
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Simplecs - Simple ECS for C#
2 | ============================
3 |
4 |
5 |
6 | Simple Entity-Component-System library for C# with no dependencies on any game engine.
7 |
8 | Pronounced "simplex."
9 |
10 | This library is not the fastest nor most powerful. It's useful for simple cases. More
11 | sophisticated games or users may need to look elsewhere for a suitable library.
12 | The intended use cases are small demos, toys, and examples.
13 |
14 | This library uses a naive approach based on sparse sets to implement O(1) component
15 | lookup. Iteration of a component table is cache-friendly, but iteration of a `View`
16 | over multiple components or using required/excluded components will incur non-cache-
17 | friendly memory access patterns.
18 |
19 | The ECS concept of a "System" is actually not found in this library. A `View` can be used
20 | to implement a System, but the scheduling and logic execution is up to the library user.
21 | This library primarily implements a way of allocating `Entity` keys and storing Component
22 | instances associated via these keys, along with a `View` concept to iterate Entities with
23 | a matching set of Components.
24 |
25 | A Component in Simplecs is any `struct` type that is `Attach`ed to an `Entity`. There are
26 | no other restrictions or requirements on Components.
27 |
28 | This library does not attempt to provide any form of multi-threading, scheduling,
29 | advanced debugging, serialization, or reflection. These are all up to the library user.
30 | Feature additions that keep the library simple in nature may be considered in the future.
31 |
32 | Usage
33 | -----
34 |
35 | The `World` class is the main container that stores all entities and components.
36 |
37 | ```c#
38 | var world = new World();
39 | ```
40 |
41 | Component types _must_ be `struct` types in Simplecs.
42 |
43 | ```c#
44 | struct PositionComponent {
45 | public int x, y z;
46 | }
47 | ```
48 |
49 | Entities are identified by the `Entity` type, which is just an opaque handle. An entity
50 | can be constructed on a `World` using a builder interface, which can also be used to
51 | attach components.
52 |
53 | ```c#
54 | var entity = world.CreateEntity()
55 | .Attach(new PositionComponent{x = 1, y = -2, z = 0})
56 | .Entity;
57 | ```
58 |
59 | Components can also be attached and detached after creation.
60 |
61 | ```c#
62 | world.Attach(entity, new NameComponent("Bob"));
63 | world.Detach(entity);
64 | ```
65 |
66 | Entities can be destroyed, which detaches all components. Unused entities should be
67 | destroyed to reclaim memory.
68 |
69 | ```c#
70 | world.Destroy(entity);
71 | ```
72 |
73 | Querying entities uses a `View<>` generic, which can be constructed via a builder on
74 | `World` like entities.
75 |
76 | ```c#
77 | var view = world.CreateView().Select();
78 | ```
79 |
80 | Views can be iterated to retrieve a list of entities and references to the selected
81 | components. Note that these references are fully mutable, which can be handy for some
82 | kinds of frequently-modified heavy components.
83 |
84 | ```c#
85 | var view = world.CreateView().Select();
86 | foreach (var row in view) {
87 | Console.WriteLine(row.Component2.Name);
88 | }
89 | ```
90 |
91 | A `View` can also be constructed with required components or excluded components.
92 | These components are used to find matching entities, but are not included in the
93 | iterated list of components like `Select<>` does. Excluded components are those which
94 | must not exist on an entity for it to match. Note that `Select<>` must be the final
95 | method in the builder chain.
96 |
97 | ```c#
98 | // select all entities with a position, and which also
99 | // have an actor component but do not have an AI component
100 | //
101 | var view = world.CreateView()
102 | .Require()
103 | .Exclude()
104 | .Select();
105 |
106 | // move all non-AI actors slightly to the right
107 | //
108 | foreach (var row in view) {
109 | row.Component.x += 1;
110 | }
111 | ```
112 |
113 | Additional support in the public interface exists to query which entities exist
114 | and to find all components on a given entity, which can be useful for tooling or
115 | serialization. Note that some of these interfaces necessitate boxing components
116 | which can be a significant performance penalty.
117 |
118 | License
119 | -------
120 |
121 | Written in 2019 by Sean Middleditch ().
122 |
123 | To the extent possible under law, the author(s) have dedicated all copyright
124 | and related and neighboring rights to this software to the public domain worldwide.
125 | This software is distributed without any warranty.
126 |
127 | You should have received a copy of the CC0 Public Domain Dedication along
128 | with this software. If not, see .
129 |
--------------------------------------------------------------------------------
/ReleaseNotes.md:
--------------------------------------------------------------------------------
1 | Simplecs Release Notes
2 | ======================
3 |
4 | Alpha 0.1.1
5 | -----------
6 |
7 | - Small performance improvements for `View`s with large selected tables
8 | but small sets of required components.
9 |
10 | Alpha 0.1.0
11 | -----------
12 |
13 | Initial release.
14 |
--------------------------------------------------------------------------------
/Simplecs.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26124.0
5 | MinimumVisualStudioVersion = 15.0.26124.0
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Simplecs", "Simplecs\Simplecs.csproj", "{F0081220-8152-4F4F-AF61-2013026406D0}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimplecsTests", "SimplecsTests\SimplecsTests.csproj", "{DA32C3B9-CE4A-4412-9CF6-731A7C5AAE3A}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Debug|x64 = Debug|x64
14 | Debug|x86 = Debug|x86
15 | Release|Any CPU = Release|Any CPU
16 | Release|x64 = Release|x64
17 | Release|x86 = Release|x86
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
23 | {F0081220-8152-4F4F-AF61-2013026406D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {F0081220-8152-4F4F-AF61-2013026406D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {F0081220-8152-4F4F-AF61-2013026406D0}.Debug|x64.ActiveCfg = Debug|Any CPU
26 | {F0081220-8152-4F4F-AF61-2013026406D0}.Debug|x64.Build.0 = Debug|Any CPU
27 | {F0081220-8152-4F4F-AF61-2013026406D0}.Debug|x86.ActiveCfg = Debug|Any CPU
28 | {F0081220-8152-4F4F-AF61-2013026406D0}.Debug|x86.Build.0 = Debug|Any CPU
29 | {F0081220-8152-4F4F-AF61-2013026406D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
30 | {F0081220-8152-4F4F-AF61-2013026406D0}.Release|Any CPU.Build.0 = Release|Any CPU
31 | {F0081220-8152-4F4F-AF61-2013026406D0}.Release|x64.ActiveCfg = Release|Any CPU
32 | {F0081220-8152-4F4F-AF61-2013026406D0}.Release|x64.Build.0 = Release|Any CPU
33 | {F0081220-8152-4F4F-AF61-2013026406D0}.Release|x86.ActiveCfg = Release|Any CPU
34 | {F0081220-8152-4F4F-AF61-2013026406D0}.Release|x86.Build.0 = Release|Any CPU
35 | {DA32C3B9-CE4A-4412-9CF6-731A7C5AAE3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36 | {DA32C3B9-CE4A-4412-9CF6-731A7C5AAE3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
37 | {DA32C3B9-CE4A-4412-9CF6-731A7C5AAE3A}.Debug|x64.ActiveCfg = Debug|Any CPU
38 | {DA32C3B9-CE4A-4412-9CF6-731A7C5AAE3A}.Debug|x64.Build.0 = Debug|Any CPU
39 | {DA32C3B9-CE4A-4412-9CF6-731A7C5AAE3A}.Debug|x86.ActiveCfg = Debug|Any CPU
40 | {DA32C3B9-CE4A-4412-9CF6-731A7C5AAE3A}.Debug|x86.Build.0 = Debug|Any CPU
41 | {DA32C3B9-CE4A-4412-9CF6-731A7C5AAE3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
42 | {DA32C3B9-CE4A-4412-9CF6-731A7C5AAE3A}.Release|Any CPU.Build.0 = Release|Any CPU
43 | {DA32C3B9-CE4A-4412-9CF6-731A7C5AAE3A}.Release|x64.ActiveCfg = Release|Any CPU
44 | {DA32C3B9-CE4A-4412-9CF6-731A7C5AAE3A}.Release|x64.Build.0 = Release|Any CPU
45 | {DA32C3B9-CE4A-4412-9CF6-731A7C5AAE3A}.Release|x86.ActiveCfg = Release|Any CPU
46 | {DA32C3B9-CE4A-4412-9CF6-731A7C5AAE3A}.Release|x86.Build.0 = Release|Any CPU
47 | EndGlobalSection
48 | EndGlobal
49 |
--------------------------------------------------------------------------------
/Simplecs/Containers/ChunkedStorage.cs:
--------------------------------------------------------------------------------
1 | // Simplecs - Simple ECS for C#
2 | //
3 | // Written in 2019 by Sean Middleditch (http://github.com/seanmiddleditch)
4 | //
5 | // To the extent possible under law, the author(s) have dedicated all copyright
6 | // and related and neighboring rights to this software to the public domain
7 | // worldwide. This software is distributed without any warranty.
8 | //
9 | // You should have received a copy of the CC0 Public Domain Dedication along
10 | // with this software. If not, see
11 | // .
12 |
13 | using System.Collections.Generic;
14 | using System.Runtime.InteropServices;
15 |
16 | namespace Simplecs.Containers {
17 | ///
18 | /// Stores a collection of values in semi-contiguous but stable memory.
19 | ///
20 | /// Backing stores are allocated in fixed-sized chunks. This storage can
21 | /// be indexed efficiently in O(1). Further, insertion is always append and
22 | /// also O(1), and removal always uses swap-and-pop and is also O(1).
23 | ///
24 | /// Note that as consequence, ordering is _not_ maintained in this container.
25 | ///
26 | /// Type stored.
27 | public class ChunkedStorage where T : struct {
28 | private readonly List _chunks = new List();
29 | private readonly int _chunkElementCount = 1024;
30 | private int _count = 0;
31 |
32 | ///
33 | /// Default size of chunks in bytes.
34 | ///
35 | public const int defaultChunkSize = 16 * 1024;
36 |
37 | ///
38 | /// Number of elements stored.
39 | ///
40 | public int Count => _count;
41 |
42 | ///
43 | /// Creates a new chunked storage container.
44 | ///
45 | /// Size of chunks in bytes.
46 | public ChunkedStorage(int chunkSize = defaultChunkSize) {
47 | int elementSize = Marshal.SizeOf();
48 | _chunkElementCount = chunkSize / elementSize;
49 | }
50 |
51 | ///
52 | /// Adds a value to container.
53 | ///
54 | /// Value to add.
55 | public void Add(in T value) {
56 | var (chunk, index) = AllocateSlot();
57 | chunk[index] = value;
58 | }
59 |
60 | ///
61 | /// Removes an element at a given index.
62 | ///
63 | /// This will swap in the last element in the container and
64 | /// hence mutates the order of elements.
65 | ///
66 | /// Index at which to remove an item.
67 | public void RemoveAt(int index) {
68 | if (index < 0 || index >= _count) {
69 | return;
70 | }
71 |
72 | if (index < _count - 1) {
73 | int chunkIndex = index / _chunkElementCount;
74 | int slotIndex = index % _chunkElementCount;
75 |
76 | _chunks[chunkIndex][slotIndex] = _chunks[_chunks.Count - 1][(_count % _chunkElementCount) - 1];
77 | }
78 |
79 | --_count;
80 | }
81 |
82 | ///
83 | /// Removes all values from the container.
84 | ///
85 | public void Clear() {
86 | _count = 0;
87 | }
88 |
89 | ///
90 | /// Accesses the value at the given index.
91 | ///
92 | public ref T this[int index] => ref _chunks[index / _chunkElementCount][index % _chunkElementCount];
93 |
94 | private (T[] chunk, int index) AllocateSlot() {
95 | int chunkIndex = _count / _chunkElementCount;
96 | int slotIndex = _count % _chunkElementCount;
97 |
98 | if (_chunks.Count == chunkIndex) {
99 | AllocateChunk();
100 | }
101 |
102 | ++_count;
103 | return (_chunks[_chunks.Count - 1], slotIndex);
104 | }
105 |
106 | private T[] AllocateChunk() {
107 | T[] chunk = new T[_chunkElementCount];
108 | _chunks.Add(chunk);
109 | return chunk;
110 | }
111 | }
112 | }
--------------------------------------------------------------------------------
/Simplecs/Containers/CircularBuffer.cs:
--------------------------------------------------------------------------------
1 | // Simplecs - Simple ECS for C#
2 | //
3 | // Written in 2019 by Sean Middleditch (http://github.com/seanmiddleditch)
4 | //
5 | // To the extent possible under law, the author(s) have dedicated all copyright
6 | // and related and neighboring rights to this software to the public domain
7 | // worldwide. This software is distributed without any warranty.
8 | //
9 | // You should have received a copy of the CC0 Public Domain Dedication along
10 | // with this software. If not, see
11 | // .
12 |
13 | using System;
14 | using System.Collections.Generic;
15 | using System.Runtime.CompilerServices;
16 |
17 | namespace Simplecs.Containers {
18 | ///
19 | /// Simple circule buffer implementation.
20 | ///
21 | public class CircularBuffer {
22 | private readonly List _data = new List();
23 | private int _head = 0;
24 | private int _count = 0;
25 |
26 | ///
27 | /// Number of elements in the container.
28 | ///
29 | public int Count => _count;
30 |
31 | ///
32 | /// Add a new element to the container at the end.
33 | ///
34 | public void Add(T value) {
35 | if (_count < _data.Count) {
36 | _data[OffsetOf(_head + _count)] = value;
37 | } else {
38 | _data.Add(value);
39 | }
40 |
41 | ++_count;
42 | }
43 |
44 | ///
45 | /// Pop an element from the front of the buffer and return it.
46 | ///
47 | public T PopFront() {
48 | if (_count == 0) {
49 | throw new InvalidOperationException(message: "CircularBuffer is empty");
50 | }
51 |
52 | T value = _data[_head];
53 | ++_head;
54 | --_count;
55 | return value;
56 | }
57 |
58 | ///
59 | /// Pop an element from the back of the buffer and return it.
60 | ///
61 | public T PopBack() {
62 | if (_count == 0) {
63 | throw new InvalidOperationException(message: "CircularBuffer is empty");
64 | }
65 |
66 | T value = _data[OffsetOf(_head + _count - 1)];
67 | --_count;
68 | return value;
69 | }
70 |
71 | [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
72 | private int OffsetOf(int index) => index % _data.Count;
73 | }
74 | }
--------------------------------------------------------------------------------
/Simplecs/Containers/ComponentTable.cs:
--------------------------------------------------------------------------------
1 | // Simplecs - Simple ECS for C#
2 | //
3 | // Written in 2019 by Sean Middleditch (http://github.com/seanmiddleditch)
4 | //
5 | // To the extent possible under law, the author(s) have dedicated all copyright
6 | // and related and neighboring rights to this software to the public domain
7 | // worldwide. This software is distributed without any warranty.
8 | //
9 | // You should have received a copy of the CC0 Public Domain Dedication along
10 | // with this software. If not, see
11 | // .
12 |
13 | using System;
14 | using System.Collections.Generic;
15 | using System.Linq;
16 |
17 | namespace Simplecs.Containers {
18 | internal interface IComponentTable {
19 | Type Type { get; }
20 | int Count { get; }
21 |
22 | bool Contains(Entity entity);
23 | bool Remove(Entity entity);
24 | void Add(Entity entity, object component);
25 | bool TryGet(Entity entity, out object data);
26 | int IndexOf(Entity entity);
27 | Entity EntityAt(int index);
28 | void Clear();
29 | }
30 |
31 | ///
32 | /// Stores a table of components mapped by unique entity keys.
33 | ///
34 | /// Struct type containing component data.
35 | internal class ComponentTable : IComponentTable where T : struct {
36 | private readonly ChunkedStorage _data = new ChunkedStorage();
37 | private readonly List _entities = new List();
38 | private readonly List _mapping = new List();
39 |
40 | public delegate void Callback(Entity entity, ref T component);
41 |
42 | public Type Type => typeof(T);
43 | public int Count => _entities.Count;
44 |
45 | public bool Contains(Entity entity) => IndexOf(entity) != -1;
46 |
47 | public int IndexOf(Entity entity) {
48 | int mappingIndex = EntityUtil.DecomposeIndex(entity);
49 |
50 | if (mappingIndex < 0 || mappingIndex >= _mapping.Count) {
51 | return -1;
52 | }
53 |
54 | int dataIndex = _mapping[mappingIndex];
55 | if (dataIndex >= _entities.Count) {
56 | return -1;
57 | }
58 |
59 | return _entities[dataIndex] == entity ? dataIndex : -1;
60 | }
61 |
62 | public bool Remove(Entity entity) {
63 | int dataIndex = IndexOf(entity);
64 | if (dataIndex == -1) {
65 | return false;
66 | }
67 |
68 | // Mark the mapping as invalid so the entity can no longer be
69 | // queried directly.
70 | //
71 | _mapping[EntityUtil.DecomposeIndex(entity)] = int.MaxValue;
72 |
73 | // Update mapping for the last component which is going to be moved into
74 | // the removed location.
75 | //
76 | int newMappingIndex = EntityUtil.DecomposeIndex(_entities[_entities.Count - 1]);
77 | _mapping[newMappingIndex] = dataIndex;
78 |
79 | // Move the component and entity information into the removed index.
80 | //
81 | _entities[dataIndex] = _entities[_entities.Count - 1];
82 | _data[dataIndex] = _data[_data.Count - 1];
83 |
84 | // Pop the final component.
85 | //
86 | _entities.RemoveAt(_entities.Count - 1);
87 | _data.RemoveAt(_data.Count - 1);
88 |
89 | return true;
90 | }
91 |
92 | public void Add(Entity entity, in T data) {
93 | int dataIndex = IndexOf(entity);
94 | if (dataIndex != -1) {
95 | _data[dataIndex] = data;
96 | return;
97 | }
98 |
99 | int mappingIndex = EntityUtil.DecomposeIndex(entity);
100 | if (mappingIndex >= _mapping.Count) {
101 | _mapping.AddRange(Enumerable.Repeat(int.MaxValue, mappingIndex - _mapping.Count + 1));
102 | }
103 |
104 | _mapping[mappingIndex] = _entities.Count;
105 |
106 | _entities.Add(entity);
107 | _data.Add(data);
108 | }
109 |
110 | void IComponentTable.Add(Entity entity, object component) {
111 | if (component.GetType() != typeof(T)) {
112 | throw new InvalidOperationException(message: "Incorrect component type");
113 | }
114 |
115 | Add(entity, (T)component);
116 | }
117 |
118 | bool IComponentTable.TryGet(Entity entity, out object data) {
119 | if (!TryGet(entity, out T component)) {
120 | data = false;
121 | return false;
122 | }
123 |
124 | data = component;
125 | return true;
126 | }
127 |
128 | public bool TryGet(Entity entity, out T data) {
129 | int dataIndex = IndexOf(entity);
130 | bool isValid = dataIndex != -1;
131 |
132 | data = isValid ? _data[dataIndex] : default(T);
133 | return isValid;
134 | }
135 |
136 | public Entity EntityAt(int index) => _entities[index];
137 |
138 | public ref T ReferenceAt(Entity entity, int index) {
139 | if (_entities[index] != entity) {
140 | throw new InvalidOperationException(message:"Dereference on invalidated binding.");
141 | }
142 |
143 | return ref _data[index];
144 | }
145 |
146 | public void Clear() {
147 | _data.Clear();
148 | _entities.Clear();
149 | }
150 | }
151 | }
--------------------------------------------------------------------------------
/Simplecs/Containers/EntityAllocator.cs:
--------------------------------------------------------------------------------
1 | // Simplecs - Simple ECS for C#
2 | //
3 | // Written in 2019 by Sean Middleditch (http://github.com/seanmiddleditch)
4 | //
5 | // To the extent possible under law, the author(s) have dedicated all copyright
6 | // and related and neighboring rights to this software to the public domain
7 | // worldwide. This software is distributed without any warranty.
8 | //
9 | // You should have received a copy of the CC0 Public Domain Dedication along
10 | // with this software. If not, see
11 | // .
12 |
13 | using System.Collections.Generic;
14 | using System.Linq;
15 | using Simplecs.Containers;
16 |
17 | namespace Simplecs.Containers {
18 | ///
19 | /// Allocates and maintains a unique set of entity keys.
20 | ///
21 | internal class EntityAllocator {
22 | private readonly List _generations = new List();
23 | private readonly CircularBuffer _freeIndices = new CircularBuffer();
24 | private int _nextUnusedIndex = 0;
25 |
26 | public const int FreeMinimum = 64;
27 | public const uint Invalid = 0;
28 |
29 | public Entity Allocate() {
30 | int index = AcquireIndex();
31 |
32 | // Determine the generation of the key at the given index;
33 | // for new indices, create a new generation.
34 | //
35 | if (index >= _generations.Count) {
36 | _generations.AddRange(Enumerable.Repeat((byte)1, index - _generations.Count + 1));
37 | }
38 |
39 | return EntityUtil.MakeKey(index, _generations[index]);
40 | }
41 |
42 | public bool Deallocate(Entity entity) {
43 | if (!IsValid(entity)) {
44 | return false;
45 | }
46 |
47 | (int index, byte generation) = EntityUtil.DecomposeKey(entity);
48 |
49 | // Bump generation so the Entity key remains stale for a long time even
50 | // when the index portion is recycled.
51 | //
52 | if (++_generations[index] == 0) {
53 | // Ensure generation can never be 0, so we can find
54 | // invalid keys easily.
55 | //
56 | _generations[index] = 1;
57 | }
58 |
59 | // Recycle index.
60 | //
61 | _freeIndices.Add(index);
62 |
63 | return true;
64 | }
65 |
66 | public bool IsValid(Entity entity) {
67 | (int index, byte generation) = EntityUtil.DecomposeKey(entity);
68 | return index >= 0 && index < _generations.Count && _generations[index] == generation;
69 | }
70 |
71 | // Only consume from the freelist if we have some items in it,
72 | // to avoid recycling the same id too often.
73 | //
74 | private int AcquireIndex() => _freeIndices.Count < FreeMinimum ? _nextUnusedIndex++ : _freeIndices.PopFront();
75 | }
76 | }
--------------------------------------------------------------------------------
/Simplecs/Entity.cs:
--------------------------------------------------------------------------------
1 | // Simplecs - Simple ECS for C#
2 | //
3 | // Written in 2019 by Sean Middleditch (http://github.com/seanmiddleditch)
4 | //
5 | // To the extent possible under law, the author(s) have dedicated all copyright
6 | // and related and neighboring rights to this software to the public domain
7 | // worldwide. This software is distributed without any warranty.
8 | //
9 | // You should have received a copy of the CC0 Public Domain Dedication along
10 | // with this software. If not, see
11 | // .
12 |
13 | using System;
14 | using System.Diagnostics.CodeAnalysis;
15 | using System.Runtime.CompilerServices;
16 |
17 | namespace Simplecs {
18 | ///
19 | /// Entity key holder.
20 | ///
21 | public readonly struct Entity : IEquatable {
22 | internal readonly uint Key;
23 |
24 | internal Entity(uint key) => Key = key;
25 |
26 | ///
27 | /// Invalid Entity constant.
28 | ///
29 | public static Entity Invalid = new Entity();
30 |
31 | ///
32 | /// Tests equality.
33 | ///
34 | [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
35 | public static bool operator ==(Entity lhs, Entity rhs) => lhs.Key == rhs.Key;
36 |
37 | ///
38 | /// Tests inequality.
39 | ///
40 | [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
41 | public static bool operator !=(Entity lhs, Entity rhs) => lhs.Key != rhs.Key;
42 |
43 | ///
44 | /// Comparison for sorted containers.
45 | ///
46 | [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
47 | public static bool operator <(Entity lhs, Entity rhs) => lhs.Key < rhs.Key;
48 |
49 | ///
50 | /// Comparison for sorted containers.
51 | ///
52 | [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
53 | public static bool operator >(Entity lhs, Entity rhs) => lhs.Key > rhs.Key;
54 |
55 | ///
56 | /// Tests equality.
57 | ///
58 | [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
59 | public bool Equals(Entity rhs) => Key == rhs.Key;
60 |
61 | ///
62 | /// Tests equality.
63 | ///
64 | public override bool Equals(object? rhs) => rhs is Entity rhsEntity && Key == rhsEntity.Key;
65 |
66 | ///
67 | /// Hash code of the entity.
68 | ///
69 | public override int GetHashCode() {
70 | return Key.GetHashCode();
71 | }
72 | }
73 |
74 | internal static class EntityUtil {
75 | [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
76 | public static Entity MakeKey(int index, byte generation = 1) {
77 | return new Entity(((uint)generation << 24) | ((uint)index & 0x00FFFFFF));
78 | }
79 |
80 | [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
81 | public static (int index, byte generation) DecomposeKey(Entity entity) {
82 | int index = DecomposeIndex(entity);
83 | byte generation = (byte)(entity.Key >> 24);
84 | return (index, generation);
85 | }
86 |
87 | [MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
88 | public static int DecomposeIndex(Entity entity) {
89 | return (int)(entity.Key & 0x00FFFFFF);
90 | }
91 | }
92 | }
--------------------------------------------------------------------------------
/Simplecs/EntityBuilder.cs:
--------------------------------------------------------------------------------
1 | // Simplecs - Simple ECS for C#
2 | //
3 | // Written in 2019 by Sean Middleditch (http://github.com/seanmiddleditch)
4 | //
5 | // To the extent possible under law, the author(s) have dedicated all copyright
6 | // and related and neighboring rights to this software to the public domain
7 | // worldwide. This software is distributed without any warranty.
8 | //
9 | // You should have received a copy of the CC0 Public Domain Dedication along
10 | // with this software. If not, see
11 | // .
12 |
13 | namespace Simplecs {
14 | ///
15 | /// Assists in creating new entities.
16 | ///
17 | public readonly struct EntityBuilder {
18 | private readonly World _world;
19 | private readonly Entity _entity;
20 |
21 | ///
22 | /// Retrieves the created entity.
23 | ///
24 | public Entity Entity => _entity;
25 |
26 | internal EntityBuilder(World world, Entity entity) {
27 | _world = world;
28 | _entity = entity;
29 | }
30 |
31 | ///
32 | /// Attaches a component to the newly created entity.
33 | ///
34 | public EntityBuilder Attach(in T component) where T : struct {
35 | _world.Attach(_entity, component);
36 | return this;
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/Simplecs/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | [assembly:InternalsVisibleTo("SimplecsTests")]
--------------------------------------------------------------------------------
/Simplecs/Simplecs.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | netcoreapp3.0
6 | 8.0
7 | true
8 |
9 |
10 |
11 |
12 | enable
13 | enable
14 | true
15 |
16 |
17 |
18 |
19 | Simplecs
20 | 0.1.2
21 | Sean Middleditch
22 | CC0-1.0
23 | https://github.com/seanmiddleditch/Simplecs
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Simplecs/ViewBuilder.cs:
--------------------------------------------------------------------------------
1 | // Simplecs - Simple ECS for C#
2 | //
3 | // Written in 2019 by Sean Middleditch (http://github.com/seanmiddleditch)
4 | //
5 | // To the extent possible under law, the author(s) have dedicated all copyright
6 | // and related and neighboring rights to this software to the public domain
7 | // worldwide. This software is distributed without any warranty.
8 | //
9 | // You should have received a copy of the CC0 Public Domain Dedication along
10 | // with this software. If not, see
11 | // .
12 |
13 | using System.Collections.Generic;
14 | using System.Linq;
15 | using Simplecs.Containers;
16 | using Simplecs.Views;
17 |
18 | namespace Simplecs {
19 | ///
20 | /// Builder for a View object.
21 | ///
22 | /// Options for required and excluded components can be set.
23 | ///
24 | /// The Select method creates the View.
25 | ///
26 | public class ViewBuilder {
27 | private readonly World _world;
28 | private List? _required;
29 | private List? _excluded;
30 |
31 | internal ViewBuilder(World world) => _world = world;
32 |
33 | ///
34 | /// Marks the specified component as required for the view,
35 | /// though it will not be in the selected component set.
36 | ///
37 | public ViewBuilder Require() where T : struct {
38 | _required ??= new List();
39 | _required.Add(_world.GetTable());
40 | return this;
41 | }
42 |
43 | ///
44 | /// Marks the specified component as excluded for the view,
45 | /// meaning that it cannot be present on matched entities.
46 | ///
47 | public ViewBuilder Exclude() where T : struct {
48 | _excluded ??= new List();
49 | _excluded.Add(_world.GetTable());
50 | return this;
51 | }
52 |
53 | ///
54 | /// Creates a View that selects the specified component.
55 | ///
56 | public View Select() where T : struct => new View(Table(), Predicate());
57 |
58 | ///
59 | /// Creates a View that selects the specified components.
60 | ///
61 | public View Select() where T1 : struct where T2 : struct => new View(Table(), Table(), Predicate());
62 |
63 | ///
64 | /// Creates a View that selects the specified components.
65 | ///
66 | public View Select() where T1 : struct where T2 : struct where T3 : struct => new View(Table(), Table(), Table(), Predicate());
67 |
68 | private ViewPredicate Predicate() => new ViewPredicate(tables: _excluded != null ? (_required != null ? _excluded.Concat(_required).ToArray() : _excluded.ToArray()) : _required?.ToArray(), excludedCount: _excluded?.Count ?? 0);
69 | private ComponentTable Table() where T : struct => _world.GetTable();
70 | }
71 | }
--------------------------------------------------------------------------------
/Simplecs/Views/EntityEnumerator.cs:
--------------------------------------------------------------------------------
1 | // Simplecs - Simple ECS for C#
2 | //
3 | // Written in 2019 by Sean Middleditch (http://github.com/seanmiddleditch)
4 | //
5 | // To the extent possible under law, the author(s) have dedicated all copyright
6 | // and related and neighboring rights to this software to the public domain
7 | // worldwide. This software is distributed without any warranty.
8 | //
9 | // You should have received a copy of the CC0 Public Domain Dedication along
10 | // with this software. If not, see
11 | // .
12 |
13 | using Simplecs.Containers;
14 |
15 | namespace Simplecs.Views {
16 | internal struct EntityEnumerator {
17 | private readonly IComponentTable _table;
18 |
19 | public Entity Entity { get; private set; }
20 | public int Index { get; private set; }
21 |
22 | public EntityEnumerator(IComponentTable table) => (_table, Entity, Index) = (table, Entity.Invalid, -1);
23 |
24 | internal bool MoveNext(IView view, out RowT row) where RowT : struct {
25 | // If the current entity has changed (e.g. been deleted from under us)
26 | // then don't initially increment the index. This allows Views to be used
27 | // to loop over entities and destroy them.
28 | //
29 | if (Index == -1 || (Index < _table.Count && Entity == _table.EntityAt(Index))) {
30 | ++Index;
31 | }
32 |
33 | while (Index < _table.Count) {
34 | Entity = _table.EntityAt(Index);
35 | if (view.TryBindRow(Entity, out row)) {
36 | return true;
37 | }
38 |
39 | ++Index;
40 | }
41 |
42 | row = default(RowT);
43 | return false;
44 | }
45 |
46 | internal void Reset() => (Entity, Index) = (Entity.Invalid, -1);
47 | }
48 | }
--------------------------------------------------------------------------------
/Simplecs/Views/View.cs:
--------------------------------------------------------------------------------
1 | // Simplecs - Simple ECS for C#
2 | //
3 | // Written in 2019 by Sean Middleditch (http://github.com/seanmiddleditch)
4 | //
5 | // To the extent possible under law, the author(s) have dedicated all copyright
6 | // and related and neighboring rights to this software to the public domain
7 | // worldwide. This software is distributed without any warranty.
8 | //
9 | // You should have received a copy of the CC0 Public Domain Dedication along
10 | // with this software. If not, see
11 | // .
12 |
13 | using System;
14 | using System.Collections;
15 | using System.Collections.Generic;
16 | using Simplecs.Containers;
17 |
18 | namespace Simplecs.Views {
19 | internal static class ViewUtility {
20 | internal static IComponentTable SmallestTable(IComponentTable table, IComponentTable? extraTable) => extraTable != null && extraTable.Count < table.Count ? extraTable : table;
21 | }
22 |
23 | ///
24 | /// A collection of entities that match a particular signature.
25 | ///
26 | public interface IView : IEnumerable where RowT : struct {
27 | ///
28 | /// Retrieves the row for a given entity.
29 | ///
30 | /// True if the entity is contained by view.
31 | bool TryBindRow(Entity entity, out RowT row);
32 |
33 | ///
34 | /// Enumerator for matched entities and components.
35 | ///
36 | new ViewEnumerator GetEnumerator();
37 |
38 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
39 | IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException();
40 | }
41 |
42 | ///
43 | /// View over entities matching a specific signature.
44 | ///
45 | public sealed class View : IView> where T : struct {
46 | private readonly ViewPredicate _predicate;
47 |
48 | internal readonly ComponentTable Table;
49 |
50 | internal View(ComponentTable table, ViewPredicate predicate) => (_predicate, Table) = (predicate, table);
51 |
52 | bool IView>.TryBindRow(Entity entity, out ViewRow row) {
53 | row = default(ViewRow);
54 | int index = Table.IndexOf(entity);
55 | if (index == -1) return false;
56 | row = new ViewRow(this, entity, index);
57 | return _predicate.IsAllowed(entity);
58 | }
59 |
60 | ViewEnumerator> IView>.GetEnumerator() => new ViewEnumerator>(this, ViewUtility.SmallestTable(Table, _predicate.SmallestTable()));
61 | }
62 |
63 | ///
64 | /// View over entities matching a specific signature.
65 | ///
66 | public sealed class View : IView>
67 | where T1 : struct
68 | where T2 : struct {
69 | private readonly ViewPredicate _predicate;
70 |
71 | internal readonly ComponentTable Table1;
72 | internal readonly ComponentTable Table2;
73 |
74 | internal View(ComponentTable table1, ComponentTable table2, ViewPredicate predicate) => (_predicate, Table1, Table2) = (predicate, table1, table2);
75 |
76 | bool IView>.TryBindRow(Entity entity, out ViewRow row) {
77 | row = default(ViewRow);
78 | int index1 = Table1.IndexOf(entity);
79 | if (index1 == -1) return false;
80 | int index2 = Table2.IndexOf(entity);
81 | if (index2 == -1) return false;
82 | row = new ViewRow(this, entity, index1, index2);
83 | return _predicate.IsAllowed(entity);
84 | }
85 |
86 | ViewEnumerator> IView>.GetEnumerator() => new ViewEnumerator>(this, ViewUtility.SmallestTable(SmallestTable(), _predicate.SmallestTable()));
87 | private IComponentTable SmallestTable() {
88 | if (Table2.Count < Table1.Count) return Table2;
89 | return Table1;
90 | }
91 | }
92 |
93 | ///
94 | /// View over entities matching a specific signature.
95 | ///
96 | public sealed class View : IView>
97 | where T1 : struct
98 | where T2 : struct
99 | where T3 : struct {
100 | private readonly ViewPredicate _predicate;
101 |
102 | internal readonly ComponentTable Table1;
103 | internal readonly ComponentTable Table2;
104 | internal readonly ComponentTable Table3;
105 |
106 | internal View(ComponentTable table1, ComponentTable table2, ComponentTable table3, ViewPredicate predicate) => (_predicate, Table1, Table2, Table3) = (predicate, table1, table2, table3);
107 |
108 | bool IView>.TryBindRow(Entity entity, out ViewRow row) {
109 | row = default(ViewRow);
110 | int index1 = Table1.IndexOf(entity);
111 | if (index1 == -1) return false;
112 | int index2 = Table2.IndexOf(entity);
113 | if (index2 == -1) return false;
114 | int index3 = Table3.IndexOf(entity);
115 | if (index3 == -1) return false;
116 | row = new ViewRow(this, entity, index1, index2, index3);
117 | return _predicate.IsAllowed(entity);
118 | }
119 |
120 | ViewEnumerator> IView>.GetEnumerator() => new ViewEnumerator>(this, ViewUtility.SmallestTable(SmallestTable(), _predicate.SmallestTable()));
121 | private IComponentTable SmallestTable() {
122 | if (Table2.Count < Table1.Count && Table2.Count < Table3.Count) return Table2;
123 | if (Table3.Count < Table1.Count && Table3.Count < Table2.Count) return Table3;
124 | return Table1;
125 | }
126 | }
127 | }
--------------------------------------------------------------------------------
/Simplecs/Views/ViewEnumerator.cs:
--------------------------------------------------------------------------------
1 | // Simplecs - Simple ECS for C#
2 | //
3 | // Written in 2019 by Sean Middleditch (http://github.com/seanmiddleditch)
4 | //
5 | // To the extent possible under law, the author(s) have dedicated all copyright
6 | // and related and neighboring rights to this software to the public domain
7 | // worldwide. This software is distributed without any warranty.
8 | //
9 | // You should have received a copy of the CC0 Public Domain Dedication along
10 | // with this software. If not, see
11 | // .
12 |
13 | using System;
14 | using System.Collections;
15 | using System.Collections.Generic;
16 | using Simplecs.Containers;
17 |
18 | namespace Simplecs.Views {
19 | ///
20 | /// Enumerator for rows in a view.
21 | ///
22 | public struct ViewEnumerator : IEnumerator where RowT : struct {
23 | private readonly IView _view;
24 | private EntityEnumerator _entities;
25 | private RowT _row;
26 |
27 | internal ViewEnumerator(IView view, IComponentTable table) => (_view, _entities, _row) = (view, new EntityEnumerator(table), default(RowT));
28 |
29 | /// Current row.
30 | public RowT Current => _row;
31 | object? IEnumerator.Current => throw new NotImplementedException();
32 |
33 | /// Attempt to increment enumerator.
34 | public bool MoveNext() => _entities.MoveNext(_view, out _row);
35 | /// Reset enumerator to initial state.
36 | public void Reset() => _entities.Reset();
37 | /// Dispose of the enumerator.
38 | public void Dispose() { }
39 | }
40 | }
--------------------------------------------------------------------------------
/Simplecs/Views/ViewPredicate.cs:
--------------------------------------------------------------------------------
1 | // Simplecs - Simple ECS for C#
2 | //
3 | // Written in 2019 by Sean Middleditch (http://github.com/seanmiddleditch)
4 | //
5 | // To the extent possible under law, the author(s) have dedicated all copyright
6 | // and related and neighboring rights to this software to the public domain
7 | // worldwide. This software is distributed without any warranty.
8 | //
9 | // You should have received a copy of the CC0 Public Domain Dedication along
10 | // with this software. If not, see
11 | // .
12 |
13 | using Simplecs.Containers;
14 |
15 | namespace Simplecs.Views {
16 | internal struct ViewPredicate {
17 | private IComponentTable[]? _tables;
18 | private int _excludedCount;
19 |
20 | internal ViewPredicate(IComponentTable[]? tables, int excludedCount) => (_tables, _excludedCount) = (tables, excludedCount);
21 |
22 | internal bool IsAllowed(Entity entity) {
23 | // If we have no tables at all, there's nothing to check.
24 | //
25 | if (_tables == null) {
26 | return true;
27 | }
28 |
29 | // If the entity exists in any excluded table, the entity is not allowed.
30 | //
31 | for (int index = 0; index != _excludedCount; ++index) {
32 | if (_tables[index].Contains(entity)) {
33 | return false;
34 | }
35 | }
36 |
37 | // If the entity is missing from any required table, the entity is not allowed.
38 | //
39 | for (int index = _excludedCount; index != _tables.Length; ++index) {
40 | if (!_tables[index].Contains(entity)) {
41 | return false;
42 | }
43 | }
44 |
45 | // The entity was not rejected by any test so it must be allowed.
46 | //
47 | return true;
48 | }
49 |
50 | internal IComponentTable? SmallestTable() {
51 | IComponentTable? bestTable = null;
52 | int bestCount = int.MaxValue;
53 |
54 | if (_tables == null) {
55 | return null;
56 | }
57 |
58 | // Find the smallest _required component_ table
59 | //
60 | for (int index = _excludedCount; index != _tables.Length; ++index) {
61 | if (_tables[index].Count < bestCount) {
62 | bestTable = _tables[index];
63 | }
64 | }
65 |
66 | return bestTable;
67 | }
68 | }
69 | }
--------------------------------------------------------------------------------
/Simplecs/Views/ViewRow.cs:
--------------------------------------------------------------------------------
1 | // Simplecs - Simple ECS for C#
2 | //
3 | // Written in 2019 by Sean Middleditch (http://github.com/seanmiddleditch)
4 | //
5 | // To the extent possible under law, the author(s) have dedicated all copyright
6 | // and related and neighboring rights to this software to the public domain
7 | // worldwide. This software is distributed without any warranty.
8 | //
9 | // You should have received a copy of the CC0 Public Domain Dedication along
10 | // with this software. If not, see
11 | // .
12 |
13 | namespace Simplecs.Views {
14 | ///
15 | /// Single row or set of components for a given view.
16 | ///
17 | public readonly struct ViewRow where T : struct {
18 | private readonly View _view;
19 | private readonly int _index;
20 |
21 | /// Entity of current row.
22 | public Entity Entity { get; }
23 | /// Component of current row.
24 | public ref T Component => ref _view.Table.ReferenceAt(Entity, _index);
25 |
26 | internal ViewRow(View view, Entity entity, int index) => (_view, Entity, _index) = (view, entity, index);
27 | }
28 |
29 | ///
30 | /// Single row or set of components for a given view.
31 | ///
32 | public readonly struct ViewRow
33 | where T1 : struct
34 | where T2 : struct {
35 | private readonly View _view;
36 | private readonly int _index1, _index2;
37 |
38 | /// Entity of current row.
39 | public Entity Entity { get; }
40 | /// First component of current row.
41 | public ref T1 Component1 => ref _view.Table1.ReferenceAt(Entity, _index1);
42 | /// Second component of current row.
43 | public ref T2 Component2 => ref _view.Table2.ReferenceAt(Entity, _index2);
44 |
45 | internal ViewRow(View view, Entity entity, int index1, int index2) => (_view, Entity, _index1, _index2) = (view, entity, index1, index2);
46 | }
47 |
48 | ///
49 | /// Single row or set of components for a given view.
50 | ///
51 | public readonly struct ViewRow
52 | where T1 : struct
53 | where T2 : struct
54 | where T3 : struct {
55 | private readonly View _view;
56 | private readonly int _index1, _index2, _index3;
57 |
58 | /// Entity of current row.
59 | public Entity Entity { get; }
60 | /// First component of current row.
61 | public ref T1 Component1 => ref _view.Table1.ReferenceAt(Entity, _index1);
62 | /// Second component of current row.
63 | public ref T2 Component2 => ref _view.Table2.ReferenceAt(Entity, _index2);
64 | /// Third component of current row.
65 | public ref T3 Component3 => ref _view.Table3.ReferenceAt(Entity, _index3);
66 |
67 | internal ViewRow(View view, Entity entity, int index1, int index2, int index3) => (_view, Entity, _index1, _index2, _index3) = (view, entity, index1, index2, index3);
68 |
69 | }
70 | }
--------------------------------------------------------------------------------
/Simplecs/World.cs:
--------------------------------------------------------------------------------
1 | // Simplecs - Simple ECS for C#
2 | //
3 | // Written in 2019 by Sean Middleditch (http://github.com/seanmiddleditch)
4 | //
5 | // To the extent possible under law, the author(s) have dedicated all copyright
6 | // and related and neighboring rights to this software to the public domain
7 | // worldwide. This software is distributed without any warranty.
8 | //
9 | // You should have received a copy of the CC0 Public Domain Dedication along
10 | // with this software. If not, see
11 | // .
12 |
13 | using System;
14 | using System.Collections.Generic;
15 | using Simplecs.Containers;
16 | using Simplecs.Views;
17 |
18 | namespace Simplecs {
19 | ///
20 | /// Contains a collection of entities and associated components.
21 | ///
22 | public sealed class World {
23 | private readonly EntityAllocator _entities = new EntityAllocator();
24 | private readonly Dictionary _tables = new Dictionary();
25 |
26 | ///
27 | /// Creates a new entity.
28 | ///
29 | /// A builder object that can be used to attach components or extract the id.
30 | public EntityBuilder CreateEntity() => new EntityBuilder(world: this, entity: _entities.Allocate());
31 |
32 | ///
33 | /// Creates a new view builder.
34 | ///
35 | /// A builder object used to construct a view.
36 | public ViewBuilder CreateView() => new ViewBuilder(this);
37 |
38 | ///
39 | /// Destroys a given entity and all associated components.
40 | ///
41 | /// Entity to destroy
42 | /// True if the given entity was found and destroyed.
43 | public bool Destroy(Entity entity) {
44 | if (!_entities.IsValid(entity)) {
45 | return false;
46 | }
47 |
48 | foreach (var table in _tables.Values) {
49 | table.Remove(entity);
50 | }
51 |
52 | return _entities.Deallocate(entity);
53 | }
54 |
55 | ///
56 | /// Checks if a given entity exists.
57 | ///
58 | /// The entity may have no components, however.
59 | ///
60 | /// Entity to test.
61 | /// True if the entity exists.
62 | public bool Exists(Entity entity) => _entities.IsValid(entity);
63 |
64 | ///
65 | /// Attaches a component to an existing entity.
66 | ///
67 | /// Entity the component will be attached to.
68 | /// Component to attach.
69 | public void Attach(Entity entity, in T component) where T : struct {
70 | if (!_entities.IsValid(entity)) {
71 | throw new InvalidOperationException(message: "Invalid entity key");
72 | }
73 |
74 | var table = GetTable();
75 | table.Add(entity, component);
76 | }
77 |
78 | ///
79 | /// Attaches a component to an existing entity.
80 | ///
81 | /// Entity the component will be attached to.
82 | /// Component to attach.
83 | public void Attach(Entity entity, object component) {
84 | if (!_entities.IsValid(entity)) {
85 | throw new InvalidOperationException(message: "Invalid entity key");
86 | }
87 |
88 | var table = GetTable(component.GetType());
89 | table.Add(entity, component);
90 | }
91 |
92 | ///
93 | /// Removes a component from an existing entity.
94 | ///
95 | /// Entity whose component should be destroyed.
96 | /// True if the given component was found and destroyed.
97 | public bool Detach(Entity entity) where T : struct {
98 | _tables.TryGetValue(typeof(T), out IComponentTable? table);
99 | var components = table as ComponentTable;
100 | return components != null && components.Remove(entity);
101 | }
102 |
103 | ///
104 | /// Removes a component from an existing entity.
105 | ///
106 | /// Entity whose component should be destroyed.
107 | /// /// Type of component that should be destroyed.
108 | /// True if the given component was found and destroyed.
109 | public bool Detach(Entity entity, Type componentType) {
110 | return _tables.TryGetValue(componentType, out IComponentTable? table) && table != null && table.Remove(entity);
111 | }
112 |
113 | ///
114 | /// Checks if the specified entity has the specified component type attached.
115 | ///
116 | public bool HasComponent(Entity entity) where T : struct {
117 | return HasComponent(entity, typeof(T));
118 | }
119 |
120 | ///
121 | /// Checks if the specified entity has the specified component type attached.
122 | ///
123 | public bool HasComponent(Entity entity, Type component) {
124 | return _tables.TryGetValue(component, out IComponentTable? table) && table != null && table.Contains(entity);
125 | }
126 |
127 | ///
128 | /// Retrives the component on the specified entity.null
129 | ///
130 | /// Illegal to call if the component does not exist.
131 | ///
132 | public ref T GetComponent(Entity entity) where T : struct {
133 | if (!_tables.TryGetValue(typeof(T), out IComponentTable? generic) || !(generic is ComponentTable typed)) {
134 | throw new InvalidOperationException(message: $"Unknown component type: {typeof(T).FullName}");
135 | }
136 |
137 | int index = typed.IndexOf(entity);
138 | if (index == -1) {
139 | throw new InvalidOperationException(message: "Entity does not have requested component");
140 | }
141 |
142 | return ref typed.ReferenceAt(entity, index);
143 | }
144 |
145 | ///
146 | /// Retrives the component on the specified entity.null
147 | ///
148 | /// Illegal to call if the component does not exist.
149 | ///
150 | public object GetComponent(Entity entity, Type component) {
151 | if (!_tables.TryGetValue(component, out IComponentTable? table) || table == null || !table.TryGet(entity, out object data)) {
152 | throw new InvalidOperationException(message: "Entity does not have requested component");
153 | }
154 |
155 | return data;
156 | }
157 |
158 | ///
159 | /// Enumerates all the components attached to a given entity.
160 | ///
161 | /// Note that these are returned as objects, so boxing for each component will
162 | /// be involved! Further, modifications to these instances will not apply
163 | /// automatically to the stored components; if modified, components should be
164 | /// re-Attached.
165 | ///
166 | /// Entity whose components should be enumerated.
167 | /// Iterator of boxed copies of the component on the entity.
168 | public IEnumerable ComponentsOn(Entity entity) {
169 | if (!_entities.IsValid(entity)) {
170 | throw new InvalidOperationException(message: "Invalid entity key");
171 | }
172 |
173 | foreach ((var componentType, IComponentTable table) in _tables) {
174 | if (table.Contains(entity)) {
175 | yield return componentType;
176 | }
177 | }
178 | }
179 |
180 | internal ComponentTable GetTable() where T : struct {
181 | if (_tables.TryGetValue(typeof(T), out IComponentTable? generic) && generic is ComponentTable typed) {
182 | return typed;
183 | }
184 |
185 | var table = new ComponentTable();
186 | _tables.Add(table.Type, table);
187 | return table;
188 | }
189 |
190 | internal IComponentTable GetTable(Type type) {
191 | if (_tables.TryGetValue(type, out IComponentTable? generic) && generic != null) {
192 | return generic;
193 | }
194 |
195 | var table = CreateTable(type);
196 | _tables.Add(table.Type, table);
197 | return table;
198 | }
199 |
200 | private IComponentTable CreateTable(Type type) {
201 | if (!type.IsValueType) {
202 | throw new InvalidOperationException(message: "Component types must be value types");
203 | }
204 |
205 | var tableGenericType = typeof(ComponentTable<>);
206 | var tableType = tableGenericType.MakeGenericType(type);
207 |
208 | var tableObject = Activator.CreateInstance(tableType);
209 | var table = tableType as IComponentTable;
210 | if (table == null) {
211 | throw new InvalidCastException(message: "Failed to cast created table to IComponentTable");
212 | }
213 |
214 | return (IComponentTable)table;
215 | }
216 | }
217 | }
--------------------------------------------------------------------------------
/SimplecsTests/ComponentTableTest.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using Simplecs;
3 | using Simplecs.Containers;
4 | using System.Linq;
5 |
6 | namespace SimplecsTests {
7 | public class ComponentTableTest {
8 | static readonly Entity key1 = EntityUtil.MakeKey(7);
9 | static readonly Entity key2 = EntityUtil.MakeKey(90, generation: 4);
10 | static readonly Entity key3 = EntityUtil.MakeKey(2, generation: 2);
11 |
12 | [Test]
13 | public void Set() {
14 | var table = new ComponentTable();
15 |
16 | table.Add(key1, new NameComponent { name = "Bob" });
17 | table.Add(key2, new NameComponent { name = "Susan" });
18 | table.Add(key3, new NameComponent { name = "Frank" });
19 |
20 | Assert.AreEqual(expected: 3, actual: table.Count);
21 | }
22 |
23 | [Test]
24 | public void SetReplace() {
25 | var table = new ComponentTable();
26 |
27 | table.Add(key1, new NameComponent { name = "Bob" });
28 | table.Add(key1, new NameComponent { name = "Susan" });
29 |
30 | Assert.AreEqual(expected: 1, table.Count);
31 | Assert.AreEqual(expected: key1, actual: table.EntityAt(0));
32 | Assert.AreEqual(expected: "Susan", actual: table.TryGet(key1, out var name) ? name.name : null);
33 | }
34 |
35 | [Test]
36 | public void Cycle() {
37 | var table = new ComponentTable();
38 |
39 | table.Add(key1, new NameComponent { name = "Bob" });
40 | table.Add(key3, new NameComponent { name = "Frank" });
41 | Assert.AreEqual(expected: 2, actual: table.Count);
42 | Assert.AreEqual(expected: key1, actual: table.EntityAt(0));
43 | Assert.AreEqual(expected: "Bob", actual: table.TryGet(key1, out var name) ? name.name : null);
44 |
45 | Assert.IsTrue(table.Remove(key1));
46 | Assert.IsFalse(table.Remove(key1));
47 | Assert.AreEqual(expected: 1, actual: table.Count);
48 |
49 | Assert.AreEqual(expected: key3, actual: table.EntityAt(0));
50 | Assert.AreEqual(expected: "Frank", actual: table.TryGet(key3, out name) ? name.name : null);
51 |
52 | table.Add(key2, new NameComponent { name = "Susan" });
53 | Assert.AreEqual(expected: 2, actual: table.Count);
54 |
55 | Assert.IsTrue(table.Remove(key3));
56 | Assert.AreEqual(expected: 1, actual: table.Count);
57 |
58 | Assert.AreEqual(expected: key2, actual: table.EntityAt(0));
59 | Assert.AreEqual(expected: "Susan", actual: table.TryGet(key2, out name) ? name.name : null);
60 | }
61 |
62 | [Test]
63 | public void Index() {
64 | var table = new ComponentTable();
65 |
66 | table.Add(key1, new IntComponent { x = 1 });
67 | table.Add(key2, new IntComponent { x = 2 });
68 | table.Add(key3, new IntComponent { x = 3 });
69 |
70 | Assert.AreEqual(expected: 2, actual: table.TryGet(key2, out var intComp) ? intComp.x : 0);
71 | Assert.AreEqual(expected: 3, actual: table.TryGet(key3, out intComp) ? intComp.x : 0);
72 |
73 | table.Remove(key2);
74 |
75 | table.Add(key2, new IntComponent { x = 4 });
76 |
77 | Assert.AreEqual(expected: 4, actual: table.TryGet(key2, out intComp) ? intComp.x : 0);
78 | Assert.AreEqual(expected: 3, actual: table.TryGet(key3, out intComp) ? intComp.x : 0);
79 | }
80 | }
81 | }
--------------------------------------------------------------------------------
/SimplecsTests/Components.cs:
--------------------------------------------------------------------------------
1 | using Simplecs;
2 |
3 | namespace SimplecsTests {
4 | internal struct NameComponent {
5 | public string name;
6 | }
7 |
8 | internal struct IntComponent {
9 | public int x;
10 | }
11 | }
--------------------------------------------------------------------------------
/SimplecsTests/EntityAllocatorTest.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using NUnit.Framework;
3 | using Simplecs;
4 | using Simplecs.Containers;
5 |
6 | namespace SimplecsTests {
7 | public class EntityAllocatorTest {
8 | [Test]
9 | public void AllocateAndFreeList() {
10 | var allocator = new EntityAllocator();
11 |
12 | Assert.AreEqual(expected: EntityUtil.MakeKey(0), actual: allocator.Allocate());
13 | Assert.AreEqual(expected: EntityUtil.MakeKey(1), actual: allocator.Allocate());
14 | Assert.AreEqual(expected: EntityUtil.MakeKey(2), actual: allocator.Allocate());
15 |
16 | // Generate enough trash to ensure ids are recycled after we delete them below.
17 | //
18 | var trash = new List(capacity: EntityAllocator.FreeMinimum);
19 | for (int counter = 0; counter != EntityAllocator.FreeMinimum; ++counter) {
20 | trash.Add(allocator.Allocate());
21 | }
22 |
23 | Assert.IsTrue(allocator.Deallocate(EntityUtil.MakeKey(1)));
24 | Assert.IsTrue(allocator.Deallocate(EntityUtil.MakeKey(2)));
25 | Assert.IsFalse(allocator.Deallocate(EntityUtil.MakeKey(EntityAllocator.FreeMinimum + 3)));
26 | Assert.IsTrue(allocator.Deallocate(EntityUtil.MakeKey(0)));
27 |
28 | // Deallocate all the trash so the free list is full
29 | //
30 | foreach (Entity trashEntity in trash) {
31 | allocator.Deallocate(trashEntity);
32 | }
33 |
34 | // Generation will be bumped for the recycled indices.
35 | //
36 | Assert.AreEqual(expected: EntityUtil.MakeKey(1, generation: 2), actual: allocator.Allocate());
37 | Assert.AreEqual(expected: EntityUtil.MakeKey(2, generation: 2), actual: allocator.Allocate());
38 | Assert.AreEqual(expected: EntityUtil.MakeKey(0, generation: 2), actual: allocator.Allocate());
39 |
40 | // Exhaust keys again so we are allocating only new entities
41 | for (int counter = 0; counter != EntityAllocator.FreeMinimum; ++counter) {
42 | allocator.Allocate();
43 | }
44 |
45 | // But not for new indices.
46 | //
47 | int totalAllocated = EntityAllocator.FreeMinimum + EntityAllocator.FreeMinimum + 2;
48 | Assert.AreEqual(expected: EntityUtil.MakeKey(totalAllocated), actual: allocator.Allocate());
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/SimplecsTests/SimplecsTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.0
5 | false
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/SimplecsTests/ViewTest.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using Simplecs;
3 | using Simplecs.Containers;
4 | using System;
5 | using System.Linq;
6 |
7 | namespace SimplecsTests {
8 | public class ViewTest {
9 | [Test]
10 | public void EachMutable() {
11 | var world = new World();
12 | world.CreateEntity().Attach(new IntComponent { x = 1 });
13 | world.CreateEntity().Attach(new IntComponent { x = 2 });
14 | world.CreateEntity().Attach(new IntComponent { x = 3 });
15 |
16 | var view = world.CreateView().Select();
17 | foreach (var row in view) {
18 | row.Component.x *= row.Component.x;
19 | }
20 |
21 | Assert.AreEqual(expected: 1, actual: view.ElementAt(0).Component.x);
22 | Assert.AreEqual(expected: 4, actual: view.ElementAt(1).Component.x);
23 | Assert.AreEqual(expected: 9, actual: view.ElementAt(2).Component.x);
24 | }
25 |
26 | [Test]
27 | public void Require() {
28 | var world = new World();
29 | world.CreateEntity().Attach(new IntComponent { x = 1 });
30 | world.CreateEntity().Attach(new NameComponent { name = "Bob" }).Attach(new IntComponent { x = 2 });
31 | world.CreateEntity().Attach(new IntComponent { x = 3 });
32 | world.CreateEntity().Attach(new NameComponent { name = "Susan" }).Attach(new IntComponent { x = 4 });
33 |
34 | var view = world.CreateView().Require().Select();
35 |
36 | Assert.AreEqual(expected: 2, actual: view.ElementAt(0).Component.x);
37 | Assert.AreEqual(expected: 4, actual: view.ElementAt(1).Component.x);
38 | }
39 |
40 | [Test]
41 | public void Exclude() {
42 | var world = new World();
43 | world.CreateEntity().Attach(new NameComponent { name = "Bob" }).Attach(new IntComponent { x = 2 });
44 | world.CreateEntity().Attach(new IntComponent { x = 1 });
45 | world.CreateEntity().Attach(new NameComponent { name = "Susan" }).Attach(new IntComponent { x = 4 });
46 | world.CreateEntity().Attach(new IntComponent { x = 3 });
47 |
48 | var view = world.CreateView().Exclude().Select();
49 |
50 | Assert.AreEqual(expected: 1, actual: view.ElementAt(0).Component.x);
51 | Assert.AreEqual(expected: 3, actual: view.ElementAt(1).Component.x);
52 | }
53 |
54 | [Test]
55 | public void Match() {
56 | var world = new World();
57 | var entity = world.CreateEntity()
58 | .Attach(new NameComponent { name = "Bob" })
59 | .Attach(new IntComponent { x = 7 })
60 | .Entity;
61 |
62 | world.CreateEntity()
63 | .Attach(new NameComponent { name = "Susan" })
64 | .Attach(new IntComponent { x = 90 });
65 |
66 | var nameView = world.CreateView().Select();
67 | var intView = world.CreateView().Select();
68 | var bothView = world.CreateView().Select();
69 |
70 | Assert.IsTrue(nameView.Any());
71 | Assert.IsTrue(intView.Any());
72 | Assert.IsTrue(bothView.Any());
73 |
74 | Assert.AreEqual(expected: new NameComponent { name = "Bob" }, actual: nameView.FirstOrDefault().Component);
75 | Assert.AreEqual(expected: new IntComponent { x = 7 }, actual: intView.FirstOrDefault().Component);
76 |
77 | Assert.AreEqual(expected: new NameComponent { name = "Bob" }, actual: bothView.FirstOrDefault().Component1);
78 | Assert.AreEqual(expected: new IntComponent { x = 7 }, actual: bothView.FirstOrDefault().Component2);
79 | }
80 |
81 | [Test]
82 | public void Access() {
83 | var world = new World();
84 | var entity1 = world.CreateEntity()
85 | .Attach(new NameComponent { name = "Bob" })
86 | .Attach(new IntComponent { x = 7 })
87 | .Entity;
88 |
89 | var entity2 = world.CreateEntity()
90 | .Attach(new NameComponent { name = "Susan" })
91 | .Attach(new IntComponent { x = 90 })
92 | .Entity;
93 |
94 | var entity3 = world.CreateEntity()
95 | .Attach(new IntComponent { x = -1 })
96 | .Entity;
97 |
98 | var view = world.CreateView().Select();
99 |
100 | Assert.IsTrue(view.Any(row => row.Entity == entity1));
101 | Assert.IsTrue(view.Any(row => row.Entity == entity2));
102 | Assert.IsFalse(view.Any(row => row.Entity == entity3));
103 |
104 | Assert.AreEqual(expected: 7, actual: view.Table1.TryGet(entity1, out var comp1) ? comp1.x : 0);
105 | Assert.AreEqual(expected: 90, actual: view.Table1.TryGet(entity2, out var comp2) ? comp2.x : 0);
106 | }
107 |
108 | [Test]
109 | public void DestroyLoop() {
110 | var world = new World();
111 |
112 | var view = world.CreateView().Select();
113 |
114 | for (int index = 0; index < EntityAllocator.FreeMinimum; ++index) {
115 | world.CreateEntity().Attach(new IntComponent());
116 | }
117 |
118 | Assert.AreEqual(expected: EntityAllocator.FreeMinimum, actual: view.Count());
119 |
120 | foreach (var row in view) {
121 | world.Destroy(row.Entity);
122 | }
123 |
124 | Assert.AreEqual(expected: 0, actual: view.Count());
125 | }
126 |
127 | // [Test]
128 | // public void Invalidate() {
129 | // var world = new World();
130 | // world.CreateEntity().Attach(new IntComponent { x = 7 });
131 | // var entity = world.CreateEntity().Attach(new IntComponent { x = 9 }).Entity;
132 | // world.CreateEntity().Attach(new IntComponent { x = 11 });
133 |
134 | // var view = world.CreateView().Select();
135 |
136 | // Assert.Throws(() => {
137 | // foreach (var row in view) {
138 | // world.Destroy(entity);
139 | // }
140 | // });
141 | // }
142 | }
143 | }
--------------------------------------------------------------------------------
/SimplecsTests/WorldTest.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using Simplecs;
3 | using System;
4 | using System.Linq;
5 |
6 | namespace SimplecsTests {
7 | public class WorldTest {
8 | [Test]
9 | public void Create() {
10 | var world = new World();
11 | var entity = world.CreateEntity()
12 | .Attach(new NameComponent { name = "Bob" })
13 | .Attach(new IntComponent { x = 7 })
14 | .Entity;
15 |
16 | world.CreateEntity()
17 | .Attach(new NameComponent { name = "Susan" })
18 | .Attach(new IntComponent { x = 90 });
19 |
20 | var names = world.GetTable();
21 | var ints = world.GetTable();
22 |
23 | Assert.AreEqual(expected: 2, actual: names.Count);
24 | Assert.AreEqual(expected: 2, actual: ints.Count);
25 | }
26 |
27 | [Test]
28 | public void Destroy() {
29 | var world = new World();
30 | var entity = world.CreateEntity()
31 | .Attach(new NameComponent { name = "Bob" })
32 | .Entity;
33 |
34 | world.CreateEntity()
35 | .Attach(new NameComponent { name = "Susan" })
36 | .Attach(new IntComponent { x = 90 });
37 |
38 | var names = world.GetTable();
39 | var ints = world.GetTable();
40 |
41 | Assert.AreEqual(expected: 2, actual: names.Count);
42 | Assert.AreEqual(expected: 1, actual: ints.Count);
43 |
44 | world.Destroy(entity);
45 |
46 | Assert.AreEqual(expected: 1, actual: names.Count);
47 | Assert.AreEqual(expected: 1, actual: ints.Count);
48 | }
49 |
50 | [Test]
51 | public void ComponentsOn() {
52 | var world = new World();
53 | var entity = world.CreateEntity()
54 | .Attach(new NameComponent { name = "Susan" })
55 | .Attach(new IntComponent { x = 90 })
56 | .Entity;
57 |
58 | Assert.AreEqual(expected: 2, world.ComponentsOn(entity).Count());
59 |
60 | Type firstType = world.ComponentsOn(entity).ElementAt(0);
61 | Type secondType = world.ComponentsOn(entity).ElementAt(1);
62 |
63 | object first = world.GetComponent(entity, firstType);
64 | object second = world.GetComponent(entity, secondType);
65 |
66 | // Iteration order isn't guaranteed so be careful about assumptions here.
67 | //
68 | if (firstType == typeof(IntComponent)) {
69 | Assert.AreEqual(expected: typeof(NameComponent), actual: secondType);
70 | Assert.AreEqual(expected: 90, actual: ((IntComponent)first).x);
71 | Assert.AreEqual(expected: "Susan", actual: ((NameComponent)second).name);
72 | } else if (firstType == typeof(NameComponent)) {
73 | Assert.AreEqual(expected: typeof(NameComponent), actual: firstType);
74 | Assert.AreEqual(expected: "Susan", actual: ((NameComponent)first).name);
75 | Assert.AreEqual(expected: 90, actual: ((IntComponent)second).x);
76 | } else {
77 | Assert.Fail(message: $"Incorrect type retrieved by world.ComponentsOn: {firstType?.ToString()}");
78 | }
79 | }
80 | }
81 | }
--------------------------------------------------------------------------------
/omnisharp.json:
--------------------------------------------------------------------------------
1 | {
2 | "FormattingOptions": {
3 | "NewLinesForBracesInLambdaExpressionBody": false,
4 | "NewLinesForBracesInAnonymousMethods": false,
5 | "NewLinesForBracesInAnonymousTypes": false,
6 | "NewLinesForBracesInControlBlocks": false,
7 | "NewLinesForBracesInTypes": false,
8 | "NewLinesForBracesInMethods": false,
9 | "NewLinesForBracesInProperties": false,
10 | "NewLinesForBracesInObjectCollectionArrayInitializers": false,
11 | "NewLinesForBracesInAccessors": false,
12 | "NewLineForElse": false,
13 | "NewLineForCatch": false,
14 | "NewLineForFinally": false
15 | }
16 | }
--------------------------------------------------------------------------------