├── .editorconfig
├── .github
├── dependabot.yml
└── workflows
│ ├── build.yml
│ ├── cmake.yml
│ └── test.yml
├── .gitignore
├── .gitmodules
├── CMakeLists.txt
├── CMakeSettings.json
├── Folder.DotSettings
├── LICENSE
├── Poltergeist.Core.Tests
├── AssertUtils.cs
├── CoTaskMemoryAllocatorTests.cs
├── EnumUtilsTests.cs
├── EnumerableUtilsTests.cs
├── NativeArrayPoolTests.cs
├── NativeArrayTests.cs
├── NativeListTests.cs
├── NativeStructTests.cs
├── PointerUtilsTests.cs
├── Poltergeist.Core.Tests.csproj
└── StringExtensionsTests.cs
├── Poltergeist.Core
├── Collections
│ └── EnumerableUtils.cs
├── ExitHandler.cs
├── Extensions
│ └── StringExtensions.cs
├── IO
│ └── BetterConsole.cs
├── Math
│ ├── EnumUtils.cs
│ ├── MathUtils.cs
│ ├── Vector2.cs
│ ├── Vector2Int.cs
│ ├── Vector3.cs
│ ├── Vector3Int.cs
│ ├── Vector4.cs
│ └── Vector4Int.cs
├── Memory
│ ├── CoTaskMemAllocator.cs
│ ├── INativeAllocator.cs
│ ├── NativeArray.cs
│ ├── NativeArrayPool.cs
│ ├── NativeList.cs
│ ├── NativeStruct.cs
│ └── PointerUtils.cs
└── Poltergeist.Core.csproj
├── Poltergeist.GameKit.sln
├── PoltergeistEditor
├── CMakeLists.txt
├── include
│ ├── .gitkeep
│ └── ImGUIContent.hpp
└── src
│ ├── ImGUIContent.cpp
│ └── PoltergeistEditor.cpp
├── PoltergeistEngine
├── CMakeLists.txt
├── include
│ ├── .gitkeep
│ └── PoltergeistEngine
│ │ ├── Encoding
│ │ └── EncodingUtilities.hpp
│ │ ├── IO
│ │ └── FileUtilities.hpp
│ │ ├── Image
│ │ └── Image.hpp
│ │ ├── Macros.hpp
│ │ └── Rendering
│ │ ├── FrameBuffer.hpp
│ │ ├── IndexBuffer.hpp
│ │ ├── OpenGlUtilities.hpp
│ │ ├── Renderer.hpp
│ │ ├── Shader.hpp
│ │ ├── ShaderStage.hpp
│ │ ├── ShaderStageType.hpp
│ │ ├── Texture.hpp
│ │ ├── Vertex.hpp
│ │ ├── VertexArray.hpp
│ │ ├── VertexBuffer.hpp
│ │ ├── VertexBufferLayout.hpp
│ │ └── VertexBufferLayoutElement.hpp
├── resources
│ ├── texture.png
│ └── white.png
├── shaders
│ ├── core.frag
│ └── core.vert
└── src
│ └── PoltergeistEngine
│ ├── Encoding
│ └── EncodingUtilities.cpp
│ ├── IO
│ └── FileUtilities.cpp
│ ├── Image
│ └── Image.cpp
│ └── Rendering
│ ├── FrameBuffer.cpp
│ ├── IndexBuffer.cpp
│ ├── Renderer.cpp
│ ├── Shader.cpp
│ ├── ShaderStage.cpp
│ ├── Texture.cpp
│ ├── VertexArray.cpp
│ └── VertexBuffer.cpp
├── PoltergeistSandbox
├── CMakeLists.txt
├── include
│ └── .gitkeep
└── src
│ └── PoltergeistSandbox.cpp
├── README.md
├── nuget.config
└── vcpkg.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.{cs,csx,vb,vbx,cpp,hpp,c,h}]
4 | indent_style = tab
5 | charset = utf-8-bom
6 | trim_trailing_whitespace = true
7 | insert_final_newline = true
8 | max_line_length = 120
9 |
10 | [CMakeLists.txt]
11 | indent_style = tab
12 | charset = utf-8
13 | trim_trailing_whitespace = true
14 | insert_final_newline = true
15 | max_line_length = 120
16 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: nuget
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | open-pull-requests-limit: 10
8 | reviewers:
9 | - KernelErr0r
10 | assignees:
11 | - KernelErr0r
12 | - package-ecosystem: github-actions
13 | directory: "/"
14 | schedule:
15 | interval: daily
16 | open-pull-requests-limit: 10
17 | reviewers:
18 | - KernelErr0r
19 | assignees:
20 | - KernelErr0r
21 | - package-ecosystem: gitsubmodule
22 | directory: "/"
23 | schedule:
24 | interval: weekly
25 | open-pull-requests-limit: 10
26 | reviewers:
27 | - KernelErr0r
28 | assignees:
29 | - KernelErr0r
30 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build GameKit
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | name: Poltergeist GameKit build (${{matrix.runtime}} ${{matrix.configuration}})
8 | runs-on: ${{matrix.os}}
9 |
10 | strategy:
11 | matrix:
12 | os: [windows-latest]
13 | configuration: ['debug', 'release']
14 | include:
15 | - os: windows-latest
16 | runtime: 'win-x64'
17 |
18 | env:
19 | DOTNET_NOLOGO: true
20 | DOTNET_CLI_TELEMETRY_OPTOUT: true
21 |
22 | steps:
23 | - uses: actions/checkout@v2
24 | - name: Setup .NET
25 | uses: actions/setup-dotnet@v1
26 | with:
27 | dotnet-version: '5.0'
28 |
29 | - name: Restore dependencies
30 | run: dotnet restore -r ${{matrix.runtime}}
31 |
32 | - name: Build ${{matrix.project}}
33 | run: dotnet publish -c ${{matrix.configuration}} -r ${{matrix.runtime}} --no-restore
34 |
--------------------------------------------------------------------------------
/.github/workflows/cmake.yml:
--------------------------------------------------------------------------------
1 | name: CMake
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | name: Poltergeist CMake
8 | runs-on: ${{matrix.os}}
9 |
10 | env:
11 | buildDir: '${{github.workspace}}/out'
12 | llvmLocation: '${{github.workspace}}/llvm'
13 |
14 | strategy:
15 | fail-fast: false
16 | matrix:
17 | os: [windows-latest,ubuntu-20.04]
18 | compiler: ['msvc','clangcl','gcc','clang']
19 | generator: ['Visual Studio 16 2019', 'MSYS Makefiles', 'Unix Makefiles']
20 | configuration: ['Debug', 'Release']
21 | exclude:
22 | - os: windows-latest
23 | generator: 'Unix Makefiles'
24 | - os: windows-latest
25 | compiler: 'gcc'
26 | - os: windows-latest
27 | compiler: 'clang'
28 | - os: ubuntu-20.04
29 | compiler: 'clangcl'
30 | - os: ubuntu-20.04
31 | compiler: 'msvc'
32 | - os: ubuntu-20.04
33 | generator: 'Visual Studio 16 2019'
34 | - os: ubuntu-20.04
35 | generator: 'MSYS Makefiles'
36 | - compiler: 'msvc'
37 | generator: 'MSYS Makefiles'
38 | - compiler: 'clangcl'
39 | generator: 'MSYS Makefiles'
40 | - compiler: 'gcc'
41 | generator: 'Visual Studio 16 2019'
42 | - compiler: 'clang'
43 | generator: 'Visual Studio 16 2019'
44 | include:
45 | - compiler: 'clangcl'
46 | toolkit: ' -T ClangCL'
47 | # - generator: 'Visual Studio 16 2019'
48 | # cmakewrapper: ''
49 | # - generator: 'MSYS Makefiles'
50 | # cmakewrapper: 'msys2 -c'
51 | # - generator: 'Visual Studio 16 2019'
52 | # cmakewrapper: ''
53 |
54 | steps:
55 | - uses: actions/checkout@v2.3.4
56 | with:
57 | submodules: 'recursive'
58 |
59 | - if: matrix.os == 'ubuntu-20.04'
60 | name: Install dependencies
61 | run: |
62 | sudo apt update
63 | sudo apt install build-essential gcc-10 g++-10 clang llvm cmake libxinerama-dev libxcursor-dev xorg-dev libglu1-mesa-dev libtinfo6
64 |
65 | - if: matrix.generator == 'MSYS Makefiles'
66 | name: Setup MSYS2
67 | uses: msys2/setup-msys2@v2
68 |
69 | - if: matrix.compiler == 'msvc'
70 | name: Setup MSBuild
71 | uses: microsoft/setup-msbuild@v1
72 |
73 | - if: matrix.compiler == 'gcc' && matrix.os != 'windows-latest'
74 | shell: 'bash'
75 | name: Set up GCC
76 | run: |
77 | echo "CC=gcc-10" >> $GITHUB_ENV
78 | echo "CXX=g++-10" >> $GITHUB_ENV
79 |
80 | # - if: matrix.compiler == 'gcc' && matrix.os == 'windows-latest'
81 | # shell: 'msys2 {0}'
82 | # name: Set up GCC
83 | # run: |
84 | # echo "CC=gcc" >> $GITHUB_ENV
85 | # echo "CXX=g++" >> $GITHUB_ENV
86 |
87 | - if: matrix.compiler == 'clang' && matrix.os != 'windows-latest'
88 | name: Cache LLVM and Clang
89 | id: cache-llvm
90 | uses: actions/cache@v2
91 | with:
92 | path: '${{env.llvmLocation}}'
93 | key: llvm-11
94 |
95 | - if: matrix.compiler == 'clang' && matrix.os != 'windows-latest'
96 | name: Install LLVM and Clang
97 | uses: KyleMayes/install-llvm-action@v1
98 | with:
99 | version: "11"
100 | directory: '${{env.llvmLocation}}'
101 | cached: ${{ steps.cache-llvm.outputs.cache-hit }}
102 |
103 | - if: matrix.compiler == 'clang' && matrix.os != 'windows-latest'
104 | shell: 'bash'
105 | name: Enable CLANG
106 | run: |
107 | echo "CC=${{env.llvmLocation}}/bin/clang" >> $GITHUB_ENV
108 | echo "CXX=${{env.llvmLocation}}/bin/clang++" >> $GITHUB_ENV
109 |
110 | # - if: matrix.compiler == 'clang' && matrix.os == 'windows-latest'
111 | # shell: 'msys2 {0}'
112 | # name: Enable CLANG
113 | # run: |
114 | # echo "CC=clang" >> $GITHUB_ENV
115 | # echo "CXX=clang++" >> $GITHUB_ENV
116 |
117 | - name: Get latest CMake and ninja
118 | uses: lukka/get-cmake@latest
119 |
120 | - name: Run vcpkg
121 | uses: lukka/run-vcpkg@v7.4
122 | with:
123 | setupOnly: true
124 | doNotUpdateVcpkg: true
125 | vcpkgDirectory: '${{github.workspace}}/vcpkg'
126 | appendedCacheKey: ${{matrix.os}}-${{matrix.generator}}-${{matrix.compiler}}-${{matrix.toolkit}}-${{matrix.configuration}}-${{hashFiles('vcpkg.json')}}
127 | additionalCachedPaths: '${{env.buildDir}}/vcpkg_installed'
128 |
129 | - name: Build
130 | uses: lukka/run-cmake@v3.4
131 | with:
132 | # cmakeWrapperCommand: '${{matrix.cmakewrapper}}'
133 | cmakeListsOrSettingsJson: CMakeListsTxtAdvanced
134 | useVcpkgToolchainFile: true
135 | buildWithCMake: true
136 | buildWithCMakeArgs: '--config ${{matrix.configuration}}'
137 | buildDirectory: ${{env.buildDir}}
138 | cmakeListsTxtPath: '${{github.workspace}}/CMakeLists.txt'
139 | cmakeAppendedArgs: '-G "${{matrix.generator}}" -DCMAKE_BUILD_TYPE=${{matrix.configuration}}${{matrix.toolkit}}'
140 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | name: Poltergeist test (${{matrix.runtime}} ${{matrix.configuration}})
8 | runs-on: ${{matrix.os}}
9 |
10 | strategy:
11 | matrix:
12 | os: [windows-latest]
13 | configuration: ['debug', 'release']
14 | include:
15 | - os: windows-latest
16 | runtime: 'win-x64'
17 |
18 | env:
19 | DOTNET_NOLOGO: true
20 | DOTNET_CLI_TELEMETRY_OPTOUT: true
21 |
22 | steps:
23 | - uses: actions/checkout@v2
24 | - name: Setup .NET
25 | uses: actions/setup-dotnet@v1
26 | with:
27 | dotnet-version: '5.0'
28 |
29 | - name: Restore dependencies
30 | run: dotnet restore -r ${{matrix.runtime}}
31 |
32 | - name: Test
33 | run: dotnet test -c ${{matrix.configuration}} -r ${{matrix.runtime}}
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 |
144 | # NCrunch
145 | _NCrunch_*
146 | .*crunch*.local.xml
147 | nCrunchTemp_*
148 |
149 | # MightyMoose
150 | *.mm.*
151 | AutoTest.Net/
152 |
153 | # Web workbench (sass)
154 | .sass-cache/
155 |
156 | # Installshield output folder
157 | [Ee]xpress/
158 |
159 | # DocProject is a documentation generator add-in
160 | DocProject/buildhelp/
161 | DocProject/Help/*.HxT
162 | DocProject/Help/*.HxC
163 | DocProject/Help/*.hhc
164 | DocProject/Help/*.hhk
165 | DocProject/Help/*.hhp
166 | DocProject/Help/Html2
167 | DocProject/Help/html
168 |
169 | # Click-Once directory
170 | publish/
171 |
172 | # Publish Web Output
173 | *.[Pp]ublish.xml
174 | *.azurePubxml
175 | # Note: Comment the next line if you want to checkin your web deploy settings,
176 | # but database connection strings (with potential passwords) will be unencrypted
177 | *.pubxml
178 | *.publishproj
179 |
180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
181 | # checkin your Azure Web App publish settings, but sensitive information contained
182 | # in these scripts will be unencrypted
183 | PublishScripts/
184 |
185 | # NuGet Packages
186 | *.nupkg
187 | # NuGet Symbol Packages
188 | *.snupkg
189 | # The packages folder can be ignored because of Package Restore
190 | **/[Pp]ackages/*
191 | # except build/, which is used as an MSBuild target.
192 | !**/[Pp]ackages/build/
193 | # Uncomment if necessary however generally it will be regenerated when needed
194 | #!**/[Pp]ackages/repositories.config
195 | # NuGet v3's project.json files produces more ignorable files
196 | *.nuget.props
197 | *.nuget.targets
198 |
199 | # Microsoft Azure Build Output
200 | csx/
201 | *.build.csdef
202 |
203 | # Microsoft Azure Emulator
204 | ecf/
205 | rcf/
206 |
207 | # Windows Store app package directories and files
208 | AppPackages/
209 | BundleArtifacts/
210 | Package.StoreAssociation.xml
211 | _pkginfo.txt
212 | *.appx
213 | *.appxbundle
214 | *.appxupload
215 |
216 | # Visual Studio cache files
217 | # files ending in .cache can be ignored
218 | *.[Cc]ache
219 | # but keep track of directories ending in .cache
220 | !?*.[Cc]ache/
221 |
222 | # Others
223 | ClientBin/
224 | ~$*
225 | *~
226 | *.dbmdl
227 | *.dbproj.schemaview
228 | *.jfm
229 | *.pfx
230 | *.publishsettings
231 | orleans.codegen.cs
232 |
233 | # Including strong name files can present a security risk
234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
235 | #*.snk
236 |
237 | # Since there are multiple workflows, uncomment next line to ignore bower_components
238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
239 | #bower_components/
240 |
241 | # RIA/Silverlight projects
242 | Generated_Code/
243 |
244 | # Backup & report files from converting an old project file
245 | # to a newer Visual Studio version. Backup files are not needed,
246 | # because we have git ;-)
247 | _UpgradeReport_Files/
248 | Backup*/
249 | UpgradeLog*.XML
250 | UpgradeLog*.htm
251 | ServiceFabricBackup/
252 | *.rptproj.bak
253 |
254 | # SQL Server files
255 | *.mdf
256 | *.ldf
257 | *.ndf
258 |
259 | # Business Intelligence projects
260 | *.rdl.data
261 | *.bim.layout
262 | *.bim_*.settings
263 | *.rptproj.rsuser
264 | *- [Bb]ackup.rdl
265 | *- [Bb]ackup ([0-9]).rdl
266 | *- [Bb]ackup ([0-9][0-9]).rdl
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # CodeRush personal settings
303 | .cr/personal
304 |
305 | # Python Tools for Visual Studio (PTVS)
306 | __pycache__/
307 | *.pyc
308 |
309 | # Cake - Uncomment if you are using it
310 | # tools/**
311 | # !tools/packages.config
312 |
313 | # Tabs Studio
314 | *.tss
315 |
316 | # Telerik's JustMock configuration file
317 | *.jmconfig
318 |
319 | # BizTalk build output
320 | *.btp.cs
321 | *.btm.cs
322 | *.odx.cs
323 | *.xsd.cs
324 |
325 | # OpenCover UI analysis results
326 | OpenCover/
327 |
328 | # Azure Stream Analytics local run output
329 | ASALocalRun/
330 |
331 | # MSBuild Binary and Structured Log
332 | *.binlog
333 |
334 | # NVidia Nsight GPU debugger configuration file
335 | *.nvuser
336 |
337 | # MFractors (Xamarin productivity tool) working folder
338 | .mfractor/
339 |
340 | # Local History for Visual Studio
341 | .localhistory/
342 |
343 | # BeatPulse healthcheck temp database
344 | healthchecksdb
345 |
346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
347 | MigrationBackup/
348 |
349 | # Ionide (cross platform F# VS Code tools) working folder
350 | .ionide/
351 |
352 | # Rider
353 | .idea/
354 |
355 | # CMake
356 | cmake-build-debug/
357 | cmake-build-release/
358 | cmake-build-debug-visual-studio/
359 | cmake-build-release-visual-studio/
360 |
361 | out/
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "vcpkg"]
2 | path = vcpkg
3 | url = https://github.com/microsoft/vcpkg.git
4 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.15)
2 | cmake_policy(SET CMP0092 NEW)
3 |
4 | set(CMAKE_CXX_STANDARD 20)
5 | set(CMAKE_C_STANDARD 11)
6 | set(CMAKE_CXX_STANDARD_REQUIRED ON)
7 | set(CMAKE_C_STANDARD_REQUIRED ON)
8 |
9 | set(VCPKG_MANIFEST_MODE ON)
10 | set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake CACHE STRING "Vcpkg toolchain file")
11 |
12 | if (NOT CMAKE_BUILD_TYPE)
13 | set(CMAKE_BUILD_TYPE Debug)
14 | endif ()
15 |
16 | # Force configuration to the specified one
17 | get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
18 | if(isMultiConfig)
19 | set(CMAKE_CONFIGURATION_TYPES "${CMAKE_BUILD_TYPE}" CACHE STRING "" FORCE)
20 | endif ()
21 |
22 | enable_language(C)
23 | enable_language(CXX)
24 |
25 | # Warning settings
26 | if (MSVC)
27 | message("Enabling MSVC warnings")
28 | add_compile_options(/W4)
29 | else ()
30 | message("Enabling non-MSVC warnings")
31 | add_compile_options(-Wall -Wextra -pedantic)
32 | endif ()
33 |
34 | # MSVC settings
35 | if (MSVC)
36 | message("Enabling MSVC UTF-8")
37 | add_compile_options(/utf-8)
38 | endif ()
39 |
40 | # Windows settings
41 | if (WIN32)
42 | message("Enabling Windows configuration")
43 | add_definitions(-DUNICODE -D_UNICODE)
44 | add_definitions(-DWIN32_LEAN_AND_MEAN)
45 | add_definitions(-DNTDDI_VERSION=NTDDI_WIN7 -DWINVER=_WIN32_WINNT_WIN7 -D_WIN32_WINNT=_WIN32_WINNT_WIN7)
46 | endif ()
47 |
48 | # Optimization settings
49 | if (MSVC)
50 | message("Enabling MSVC optimizations")
51 | SET(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /O2 /Oi /Ot /Oy /GL")
52 | SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2 /Oi /Ot /Oy /GL")
53 | if (CMAKE_SIZEOF_VOID_P LESS 8)
54 | message("Enabling MSVC non 64bit optimizations")
55 | SET(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /arch:SSE2")
56 | SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /arch:SSE2")
57 | endif ()
58 | SET(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG")
59 | else ()
60 | message("Enabling non-MSVC optimizations")
61 | SET(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O2 -mfpmath=sse -mmmx -msse -msse2")
62 | SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -mfpmath=sse -mmmx -msse -msse2")
63 | endif ()
64 |
65 | project(Poltergeist)
66 |
67 | add_subdirectory("PoltergeistEngine")
68 | add_subdirectory("PoltergeistEditor")
69 | add_subdirectory("PoltergeistSandbox")
70 |
--------------------------------------------------------------------------------
/CMakeSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "configurations": [
3 | {
4 | "name": "x64-Debug",
5 | "generator": "Ninja",
6 | "configurationType": "Debug",
7 | "inheritEnvironments": [ "msvc_x64_x64" ],
8 | "buildRoot": "${projectDir}\\out\\build\\${name}",
9 | "installRoot": "${projectDir}\\out\\install\\${name}",
10 | "cmakeCommandArgs": "",
11 | "buildCommandArgs": "",
12 | "ctestCommandArgs": ""
13 | },
14 | {
15 | "name": "x64-Release",
16 | "generator": "Ninja",
17 | "configurationType": "Release",
18 | "inheritEnvironments": [ "msvc_x64_x64" ],
19 | "buildRoot": "${projectDir}\\out\\build\\${name}",
20 | "installRoot": "${projectDir}\\out\\install\\${name}",
21 | "cmakeCommandArgs": "",
22 | "buildCommandArgs": "",
23 | "ctestCommandArgs": ""
24 | }
25 | ]
26 | }
--------------------------------------------------------------------------------
/Folder.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | ExplicitlyExcluded
3 | ExplicitlyExcluded
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Mozilla Public License Version 2.0
2 | ==================================
3 |
4 | 1. Definitions
5 | --------------
6 |
7 | 1.1. "Contributor"
8 | means each individual or legal entity that creates, contributes to
9 | the creation of, or owns Covered Software.
10 |
11 | 1.2. "Contributor Version"
12 | means the combination of the Contributions of others (if any) used
13 | by a Contributor and that particular Contributor's Contribution.
14 |
15 | 1.3. "Contribution"
16 | means Covered Software of a particular Contributor.
17 |
18 | 1.4. "Covered Software"
19 | means Source Code Form to which the initial Contributor has attached
20 | the notice in Exhibit A, the Executable Form of such Source Code
21 | Form, and Modifications of such Source Code Form, in each case
22 | including portions thereof.
23 |
24 | 1.5. "Incompatible With Secondary Licenses"
25 | means
26 |
27 | (a) that the initial Contributor has attached the notice described
28 | in Exhibit B to the Covered Software; or
29 |
30 | (b) that the Covered Software was made available under the terms of
31 | version 1.1 or earlier of the License, but not also under the
32 | terms of a Secondary License.
33 |
34 | 1.6. "Executable Form"
35 | means any form of the work other than Source Code Form.
36 |
37 | 1.7. "Larger Work"
38 | means a work that combines Covered Software with other material, in
39 | a separate file or files, that is not Covered Software.
40 |
41 | 1.8. "License"
42 | means this document.
43 |
44 | 1.9. "Licensable"
45 | means having the right to grant, to the maximum extent possible,
46 | whether at the time of the initial grant or subsequently, any and
47 | all of the rights conveyed by this License.
48 |
49 | 1.10. "Modifications"
50 | means any of the following:
51 |
52 | (a) any file in Source Code Form that results from an addition to,
53 | deletion from, or modification of the contents of Covered
54 | Software; or
55 |
56 | (b) any new file in Source Code Form that contains any Covered
57 | Software.
58 |
59 | 1.11. "Patent Claims" of a Contributor
60 | means any patent claim(s), including without limitation, method,
61 | process, and apparatus claims, in any patent Licensable by such
62 | Contributor that would be infringed, but for the grant of the
63 | License, by the making, using, selling, offering for sale, having
64 | made, import, or transfer of either its Contributions or its
65 | Contributor Version.
66 |
67 | 1.12. "Secondary License"
68 | means either the GNU General Public License, Version 2.0, the GNU
69 | Lesser General Public License, Version 2.1, the GNU Affero General
70 | Public License, Version 3.0, or any later versions of those
71 | licenses.
72 |
73 | 1.13. "Source Code Form"
74 | means the form of the work preferred for making modifications.
75 |
76 | 1.14. "You" (or "Your")
77 | means an individual or a legal entity exercising rights under this
78 | License. For legal entities, "You" includes any entity that
79 | controls, is controlled by, or is under common control with You. For
80 | purposes of this definition, "control" means (a) the power, direct
81 | or indirect, to cause the direction or management of such entity,
82 | whether by contract or otherwise, or (b) ownership of more than
83 | fifty percent (50%) of the outstanding shares or beneficial
84 | ownership of such entity.
85 |
86 | 2. License Grants and Conditions
87 | --------------------------------
88 |
89 | 2.1. Grants
90 |
91 | Each Contributor hereby grants You a world-wide, royalty-free,
92 | non-exclusive license:
93 |
94 | (a) under intellectual property rights (other than patent or trademark)
95 | Licensable by such Contributor to use, reproduce, make available,
96 | modify, display, perform, distribute, and otherwise exploit its
97 | Contributions, either on an unmodified basis, with Modifications, or
98 | as part of a Larger Work; and
99 |
100 | (b) under Patent Claims of such Contributor to make, use, sell, offer
101 | for sale, have made, import, and otherwise transfer either its
102 | Contributions or its Contributor Version.
103 |
104 | 2.2. Effective Date
105 |
106 | The licenses granted in Section 2.1 with respect to any Contribution
107 | become effective for each Contribution on the date the Contributor first
108 | distributes such Contribution.
109 |
110 | 2.3. Limitations on Grant Scope
111 |
112 | The licenses granted in this Section 2 are the only rights granted under
113 | this License. No additional rights or licenses will be implied from the
114 | distribution or licensing of Covered Software under this License.
115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a
116 | Contributor:
117 |
118 | (a) for any code that a Contributor has removed from Covered Software;
119 | or
120 |
121 | (b) for infringements caused by: (i) Your and any other third party's
122 | modifications of Covered Software, or (ii) the combination of its
123 | Contributions with other software (except as part of its Contributor
124 | Version); or
125 |
126 | (c) under Patent Claims infringed by Covered Software in the absence of
127 | its Contributions.
128 |
129 | This License does not grant any rights in the trademarks, service marks,
130 | or logos of any Contributor (except as may be necessary to comply with
131 | the notice requirements in Section 3.4).
132 |
133 | 2.4. Subsequent Licenses
134 |
135 | No Contributor makes additional grants as a result of Your choice to
136 | distribute the Covered Software under a subsequent version of this
137 | License (see Section 10.2) or under the terms of a Secondary License (if
138 | permitted under the terms of Section 3.3).
139 |
140 | 2.5. Representation
141 |
142 | Each Contributor represents that the Contributor believes its
143 | Contributions are its original creation(s) or it has sufficient rights
144 | to grant the rights to its Contributions conveyed by this License.
145 |
146 | 2.6. Fair Use
147 |
148 | This License is not intended to limit any rights You have under
149 | applicable copyright doctrines of fair use, fair dealing, or other
150 | equivalents.
151 |
152 | 2.7. Conditions
153 |
154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
155 | in Section 2.1.
156 |
157 | 3. Responsibilities
158 | -------------------
159 |
160 | 3.1. Distribution of Source Form
161 |
162 | All distribution of Covered Software in Source Code Form, including any
163 | Modifications that You create or to which You contribute, must be under
164 | the terms of this License. You must inform recipients that the Source
165 | Code Form of the Covered Software is governed by the terms of this
166 | License, and how they can obtain a copy of this License. You may not
167 | attempt to alter or restrict the recipients' rights in the Source Code
168 | Form.
169 |
170 | 3.2. Distribution of Executable Form
171 |
172 | If You distribute Covered Software in Executable Form then:
173 |
174 | (a) such Covered Software must also be made available in Source Code
175 | Form, as described in Section 3.1, and You must inform recipients of
176 | the Executable Form how they can obtain a copy of such Source Code
177 | Form by reasonable means in a timely manner, at a charge no more
178 | than the cost of distribution to the recipient; and
179 |
180 | (b) You may distribute such Executable Form under the terms of this
181 | License, or sublicense it under different terms, provided that the
182 | license for the Executable Form does not attempt to limit or alter
183 | the recipients' rights in the Source Code Form under this License.
184 |
185 | 3.3. Distribution of a Larger Work
186 |
187 | You may create and distribute a Larger Work under terms of Your choice,
188 | provided that You also comply with the requirements of this License for
189 | the Covered Software. If the Larger Work is a combination of Covered
190 | Software with a work governed by one or more Secondary Licenses, and the
191 | Covered Software is not Incompatible With Secondary Licenses, this
192 | License permits You to additionally distribute such Covered Software
193 | under the terms of such Secondary License(s), so that the recipient of
194 | the Larger Work may, at their option, further distribute the Covered
195 | Software under the terms of either this License or such Secondary
196 | License(s).
197 |
198 | 3.4. Notices
199 |
200 | You may not remove or alter the substance of any license notices
201 | (including copyright notices, patent notices, disclaimers of warranty,
202 | or limitations of liability) contained within the Source Code Form of
203 | the Covered Software, except that You may alter any license notices to
204 | the extent required to remedy known factual inaccuracies.
205 |
206 | 3.5. Application of Additional Terms
207 |
208 | You may choose to offer, and to charge a fee for, warranty, support,
209 | indemnity or liability obligations to one or more recipients of Covered
210 | Software. However, You may do so only on Your own behalf, and not on
211 | behalf of any Contributor. You must make it absolutely clear that any
212 | such warranty, support, indemnity, or liability obligation is offered by
213 | You alone, and You hereby agree to indemnify every Contributor for any
214 | liability incurred by such Contributor as a result of warranty, support,
215 | indemnity or liability terms You offer. You may include additional
216 | disclaimers of warranty and limitations of liability specific to any
217 | jurisdiction.
218 |
219 | 4. Inability to Comply Due to Statute or Regulation
220 | ---------------------------------------------------
221 |
222 | If it is impossible for You to comply with any of the terms of this
223 | License with respect to some or all of the Covered Software due to
224 | statute, judicial order, or regulation then You must: (a) comply with
225 | the terms of this License to the maximum extent possible; and (b)
226 | describe the limitations and the code they affect. Such description must
227 | be placed in a text file included with all distributions of the Covered
228 | Software under this License. Except to the extent prohibited by statute
229 | or regulation, such description must be sufficiently detailed for a
230 | recipient of ordinary skill to be able to understand it.
231 |
232 | 5. Termination
233 | --------------
234 |
235 | 5.1. The rights granted under this License will terminate automatically
236 | if You fail to comply with any of its terms. However, if You become
237 | compliant, then the rights granted under this License from a particular
238 | Contributor are reinstated (a) provisionally, unless and until such
239 | Contributor explicitly and finally terminates Your grants, and (b) on an
240 | ongoing basis, if such Contributor fails to notify You of the
241 | non-compliance by some reasonable means prior to 60 days after You have
242 | come back into compliance. Moreover, Your grants from a particular
243 | Contributor are reinstated on an ongoing basis if such Contributor
244 | notifies You of the non-compliance by some reasonable means, this is the
245 | first time You have received notice of non-compliance with this License
246 | from such Contributor, and You become compliant prior to 30 days after
247 | Your receipt of the notice.
248 |
249 | 5.2. If You initiate litigation against any entity by asserting a patent
250 | infringement claim (excluding declaratory judgment actions,
251 | counter-claims, and cross-claims) alleging that a Contributor Version
252 | directly or indirectly infringes any patent, then the rights granted to
253 | You by any and all Contributors for the Covered Software under Section
254 | 2.1 of this License shall terminate.
255 |
256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
257 | end user license agreements (excluding distributors and resellers) which
258 | have been validly granted by You or Your distributors under this License
259 | prior to termination shall survive termination.
260 |
261 | ************************************************************************
262 | * *
263 | * 6. Disclaimer of Warranty *
264 | * ------------------------- *
265 | * *
266 | * Covered Software is provided under this License on an "as is" *
267 | * basis, without warranty of any kind, either expressed, implied, or *
268 | * statutory, including, without limitation, warranties that the *
269 | * Covered Software is free of defects, merchantable, fit for a *
270 | * particular purpose or non-infringing. The entire risk as to the *
271 | * quality and performance of the Covered Software is with You. *
272 | * Should any Covered Software prove defective in any respect, You *
273 | * (not any Contributor) assume the cost of any necessary servicing, *
274 | * repair, or correction. This disclaimer of warranty constitutes an *
275 | * essential part of this License. No use of any Covered Software is *
276 | * authorized under this License except under this disclaimer. *
277 | * *
278 | ************************************************************************
279 |
280 | ************************************************************************
281 | * *
282 | * 7. Limitation of Liability *
283 | * -------------------------- *
284 | * *
285 | * Under no circumstances and under no legal theory, whether tort *
286 | * (including negligence), contract, or otherwise, shall any *
287 | * Contributor, or anyone who distributes Covered Software as *
288 | * permitted above, be liable to You for any direct, indirect, *
289 | * special, incidental, or consequential damages of any character *
290 | * including, without limitation, damages for lost profits, loss of *
291 | * goodwill, work stoppage, computer failure or malfunction, or any *
292 | * and all other commercial damages or losses, even if such party *
293 | * shall have been informed of the possibility of such damages. This *
294 | * limitation of liability shall not apply to liability for death or *
295 | * personal injury resulting from such party's negligence to the *
296 | * extent applicable law prohibits such limitation. Some *
297 | * jurisdictions do not allow the exclusion or limitation of *
298 | * incidental or consequential damages, so this exclusion and *
299 | * limitation may not apply to You. *
300 | * *
301 | ************************************************************************
302 |
303 | 8. Litigation
304 | -------------
305 |
306 | Any litigation relating to this License may be brought only in the
307 | courts of a jurisdiction where the defendant maintains its principal
308 | place of business and such litigation shall be governed by laws of that
309 | jurisdiction, without reference to its conflict-of-law provisions.
310 | Nothing in this Section shall prevent a party's ability to bring
311 | cross-claims or counter-claims.
312 |
313 | 9. Miscellaneous
314 | ----------------
315 |
316 | This License represents the complete agreement concerning the subject
317 | matter hereof. If any provision of this License is held to be
318 | unenforceable, such provision shall be reformed only to the extent
319 | necessary to make it enforceable. Any law or regulation which provides
320 | that the language of a contract shall be construed against the drafter
321 | shall not be used to construe this License against a Contributor.
322 |
323 | 10. Versions of the License
324 | ---------------------------
325 |
326 | 10.1. New Versions
327 |
328 | Mozilla Foundation is the license steward. Except as provided in Section
329 | 10.3, no one other than the license steward has the right to modify or
330 | publish new versions of this License. Each version will be given a
331 | distinguishing version number.
332 |
333 | 10.2. Effect of New Versions
334 |
335 | You may distribute the Covered Software under the terms of the version
336 | of the License under which You originally received the Covered Software,
337 | or under the terms of any subsequent version published by the license
338 | steward.
339 |
340 | 10.3. Modified Versions
341 |
342 | If you create software not governed by this License, and you want to
343 | create a new license for such software, you may create and use a
344 | modified version of this License if you rename the license and remove
345 | any references to the name of the license steward (except to note that
346 | such modified license differs from this License).
347 |
348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary
349 | Licenses
350 |
351 | If You choose to distribute Source Code Form that is Incompatible With
352 | Secondary Licenses under the terms of this version of the License, the
353 | notice described in Exhibit B of this License must be attached.
354 |
355 | Exhibit A - Source Code Form License Notice
356 | -------------------------------------------
357 |
358 | This Source Code Form is subject to the terms of the Mozilla Public
359 | License, v. 2.0. If a copy of the MPL was not distributed with this
360 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
361 |
362 | If it is not possible or desirable to put the notice in a particular
363 | file, then You may include the notice in a location (such as a LICENSE
364 | file in a relevant directory) where a recipient would be likely to look
365 | for such a notice.
366 |
367 | You may add additional accurate notices of copyright ownership.
368 |
369 | Exhibit B - "Incompatible With Secondary Licenses" Notice
370 | ---------------------------------------------------------
371 |
372 | This Source Code Form is "Incompatible With Secondary Licenses", as
373 | defined by the Mozilla Public License, v. 2.0.
374 |
--------------------------------------------------------------------------------
/Poltergeist.Core.Tests/AssertUtils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Xunit;
5 |
6 | namespace Poltergeist.Core.Tests
7 | {
8 | public static class AssertUtils
9 | {
10 | public static void SequenceEqual(IEnumerable expected, IEnumerable actual)
11 | {
12 | if (expected is IReadOnlyList list1 && actual is IReadOnlyList list2)
13 | Assert.Equal(list1.Count, list2.Count);
14 | // ReSharper disable PossibleMultipleEnumeration
15 | Assert.Equal(expected.Count(), actual.Count());
16 | Assert.True(expected.SequenceEqual(actual));
17 | // ReSharper restore PossibleMultipleEnumeration
18 | }
19 |
20 | public static void SequenceEqual(ReadOnlySpan expected, ReadOnlySpan actual) where T : IEquatable
21 | {
22 | Assert.Equal(expected.Length, actual.Length);
23 | Assert.True(expected.SequenceEqual(actual));
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Poltergeist.Core.Tests/CoTaskMemoryAllocatorTests.cs:
--------------------------------------------------------------------------------
1 | using Poltergeist.Core.Memory;
2 | using System;
3 | using Xunit;
4 |
5 | namespace Poltergeist.Core.Tests
6 | {
7 | public class CoTaskMemoryAllocatorTests
8 | {
9 | [Theory]
10 | [InlineData(0)]
11 | [InlineData(1)]
12 | [InlineData(16)]
13 | [InlineData(1024)]
14 | public unsafe void AllocationTest(int size)
15 | {
16 | CoTaskMemAllocator allocator = new();
17 | IntPtr data = allocator.Allocate(size);
18 | try
19 | {
20 | Assert.NotEqual(IntPtr.Zero, data);
21 | byte* bytes = (byte*)data.ToPointer();
22 | // check if writing to memory doesn't crash
23 | for (int i = 0; i < size; i++)
24 | {
25 | bytes[i] = byte.MaxValue;
26 | }
27 | // check if reading memory yields correct values
28 | for (int i = 0; i < size; i++)
29 | {
30 | Assert.Equal(byte.MaxValue, bytes[i]);
31 | }
32 | }
33 | finally
34 | {
35 | allocator.Free(data);
36 | }
37 | }
38 |
39 | [Theory]
40 | [InlineData(-1)]
41 | [InlineData(int.MinValue)]
42 | public void NegativeSizeTest(int size)
43 | {
44 | Assert.Throws(() => { new CoTaskMemAllocator().Allocate(size); });
45 | }
46 |
47 | [Theory]
48 | [InlineData(0)]
49 | [InlineData(-1)]
50 | [InlineData(1)]
51 | [InlineData(int.MinValue)]
52 | [InlineData(int.MaxValue)]
53 | public void InvalidFreeTest(int data)
54 | {
55 | Assert.Throws(() => { new CoTaskMemAllocator().Free(new IntPtr(data)); });
56 | }
57 |
58 | [Theory]
59 | [InlineData(0)]
60 | [InlineData(1)]
61 | [InlineData(16)]
62 | [InlineData(1024)]
63 | public void DoubleFreeTest(int size)
64 | {
65 | CoTaskMemAllocator allocator = new();
66 | IntPtr data = allocator.Allocate(size);
67 | allocator.Free(data);
68 | Assert.Throws(() => { allocator.Free(data); });
69 | }
70 |
71 | [Theory]
72 | [InlineData(0)]
73 | [InlineData(1)]
74 | [InlineData(16)]
75 | [InlineData(1024)]
76 | public void WrongAllocatorFreeTest(int size)
77 | {
78 | CoTaskMemAllocator allocator = new();
79 | IntPtr data = allocator.Allocate(size);
80 | try
81 | {
82 | Assert.Throws(() => { new CoTaskMemAllocator().Free(data); });
83 | }
84 | finally
85 | {
86 | allocator.Free(data);
87 | }
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Poltergeist.Core.Tests/EnumUtilsTests.cs:
--------------------------------------------------------------------------------
1 | using Poltergeist.Core.Math;
2 | using System;
3 | using System.Linq;
4 | using Xunit;
5 |
6 | namespace Poltergeist.Core.Tests
7 | {
8 | public static class EnumUtilsTests
9 | {
10 | // ReSharper disable UnusedMember.Local
11 | private enum TestEnum
12 | {
13 | A = 1,
14 | B = 2,
15 | C = 4,
16 | D = A,
17 | E = B,
18 | F = 3
19 | }
20 |
21 | [Flags]
22 | public enum TestFlags
23 | {
24 | A = 1 << 0,
25 | B = 1 << 1,
26 | C = 1 << 2,
27 | D = 1 << 3,
28 | E = 1 << 4,
29 | F = 1 << 5
30 | }
31 | // ReSharper restore UnusedMember.Local
32 |
33 | [Fact]
34 | public static void ValuesTest()
35 | {
36 | Assert.True(EnumUtils.Values.ToArray().SequenceEqual(Enum.GetValues()));
37 | }
38 |
39 | [Fact]
40 | public static void NamesTest()
41 | {
42 | Assert.True(EnumUtils.Names.ToArray().SequenceEqual(Enum.GetNames()));
43 | }
44 |
45 | [Fact]
46 | public static void TypeTest()
47 | {
48 | Assert.Equal(Enum.GetUnderlyingType(typeof(TestEnum)), EnumUtils.UnderlyingType);
49 | }
50 |
51 | [Theory]
52 | [InlineData((TestFlags)0, TestFlags.C)]
53 | [InlineData(TestFlags.A, TestFlags.C)]
54 | [InlineData(TestFlags.C, TestFlags.C)]
55 | [InlineData(TestFlags.A | TestFlags.B, TestFlags.C)]
56 | [InlineData(TestFlags.A | TestFlags.C, TestFlags.C)]
57 | public static void HasFlagTest(TestFlags value, TestFlags flag)
58 | {
59 | // ReSharper disable HeapView.BoxingAllocation
60 | Assert.Equal(value.HasFlag(flag), EnumUtils.HasFlag(value, flag));
61 | Assert.Equal(value.HasFlag(flag), value.HasFlagF(flag));
62 | // ReSharper restore HeapView.BoxingAllocation
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Poltergeist.Core.Tests/EnumerableUtilsTests.cs:
--------------------------------------------------------------------------------
1 | using Poltergeist.Core.Collections;
2 | using System.Linq;
3 | using Xunit;
4 |
5 | namespace Poltergeist.Core.Tests
6 | {
7 | public class EnumerableUtilsTests
8 | {
9 | [Fact]
10 | public void JoinTest()
11 | {
12 | Assert.True(EnumerableUtils.Join(new[] { 1, 2, 3 }, new[] { 4, 5 }, new[] { 6, 7, 8 }, new[] { 9 }).SequenceEqual(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }));
13 | Assert.True(EnumerableUtils.Join(new[] { "a", "b", "c" }, new[] { "d", "e" }, new[] { "f", "g", "h" }, new[] { "i" }).SequenceEqual(new[] { "a", "b", "c", "d", "e", "f", "g", "h", "i" }));
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Poltergeist.Core.Tests/NativeArrayPoolTests.cs:
--------------------------------------------------------------------------------
1 | using Poltergeist.Core.Memory;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Threading.Tasks;
5 | using Xunit;
6 |
7 | namespace Poltergeist.Core.Tests
8 | {
9 | public class NativeArrayPoolTests
10 | {
11 | [Theory]
12 | [InlineData(0)]
13 | [InlineData(1)]
14 | [InlineData(64)]
15 | [InlineData(1024)]
16 | public void RentTest(int size)
17 | {
18 | NativeArray nativeArray = NativeArrayPool.Rent(size);
19 | try
20 | {
21 | Assert.NotNull(nativeArray);
22 | Assert.True(nativeArray.Count >= size);
23 | }
24 | finally
25 | {
26 | NativeArrayPool.Return(nativeArray);
27 | }
28 | }
29 |
30 | [Theory]
31 | [InlineData(0, 10)]
32 | [InlineData(1, 10)]
33 | [InlineData(64, 10)]
34 | [InlineData(1024, 10)]
35 | public void RentMultipleTest(int size, int count)
36 | {
37 | NativeArray[] nativeArrays = new NativeArray[count];
38 | for (int i = 0; i < count; i++)
39 | nativeArrays[i] = NativeArrayPool.Rent(size);
40 |
41 | try
42 | {
43 | foreach (NativeArray nativeArray in nativeArrays)
44 | {
45 | Assert.NotNull(nativeArray);
46 | Assert.True(nativeArray.Count >= size);
47 | }
48 | }
49 | finally
50 | {
51 | foreach (NativeArray nativeArray in nativeArrays)
52 | NativeArrayPool.Return(nativeArray);
53 | }
54 | }
55 |
56 | [Theory]
57 | [InlineData(0)]
58 | [InlineData(1)]
59 | [InlineData(64)]
60 | [InlineData(1024)]
61 | public void ParallelRentTest(int size)
62 | {
63 | Parallel.For(0, Environment.ProcessorCount * 2, (_, _) =>
64 | {
65 | NativeArray nativeArray = NativeArrayPool.Rent(size);
66 | try
67 | {
68 | Assert.NotNull(nativeArray);
69 | Assert.True(nativeArray.Count >= size);
70 | }
71 | finally
72 | {
73 | NativeArrayPool.Return(nativeArray);
74 | }
75 | });
76 | }
77 |
78 | [Theory]
79 | [InlineData(5)]
80 | [InlineData(10)]
81 | public unsafe void ZeroCacheTest(int count)
82 | {
83 | NativeArray[] nativeArrays = new NativeArray[count];
84 | for (int i = 0; i < count; i++)
85 | nativeArrays[i] = NativeArrayPool.Rent(0);
86 |
87 | try
88 | {
89 | foreach (NativeArray nativeArray in nativeArrays)
90 | {
91 | Assert.NotNull(nativeArray);
92 | Assert.Equal(0, nativeArray.Count);
93 | Assert.True(nativeArrays[0].Data == nativeArray.Data);
94 | Assert.Equal(nativeArrays[0], nativeArray);
95 | }
96 | }
97 | finally
98 | {
99 | foreach (NativeArray nativeArray in nativeArrays)
100 | NativeArrayPool.Return(nativeArray);
101 | }
102 | }
103 |
104 | [Theory]
105 | [InlineData(1, 10)]
106 | [InlineData(64, 10)]
107 | [InlineData(1024, 10)]
108 | public unsafe void UniqueTest(int size, int count)
109 | {
110 | NativeArray[] nativeArrays = new NativeArray[count];
111 | for (int i = 0; i < count; i++)
112 | nativeArrays[i] = NativeArrayPool.Rent(size);
113 |
114 | HashSet> arrays = new();
115 | HashSet datas = new();
116 |
117 | try
118 | {
119 | foreach (NativeArray nativeArray in nativeArrays)
120 | {
121 | Assert.True(arrays.Add(nativeArray));
122 | Assert.True(datas.Add(new IntPtr(nativeArray.Data)));
123 | }
124 | }
125 | finally
126 | {
127 | foreach (NativeArray nativeArray in nativeArrays)
128 | NativeArrayPool.Return(nativeArray);
129 | }
130 | }
131 |
132 | [Theory]
133 | [InlineData(-1)]
134 | [InlineData(int.MinValue)]
135 | public void NegativeTest(int size)
136 | {
137 | Assert.Throws(() => NativeArrayPool.Rent(size));
138 | }
139 |
140 | [Fact]
141 | public void ReturnNullTest()
142 | {
143 | Assert.Throws(() => NativeArrayPool.Return(null));
144 | }
145 |
146 | [Theory]
147 | [InlineData(0)]
148 | [InlineData(1)]
149 | [InlineData(10)]
150 | public void ReturnNotFromPoolTest(int size)
151 | {
152 | NativeArray array = new(size);
153 | try
154 | {
155 | Assert.Throws(() => NativeArrayPool.Return(array));
156 | }
157 | finally
158 | {
159 | array.Dispose();
160 | }
161 | }
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/Poltergeist.Core.Tests/NativeArrayTests.cs:
--------------------------------------------------------------------------------
1 | using Poltergeist.Core.Memory;
2 | using System;
3 | using System.Linq;
4 | using System.Runtime.InteropServices;
5 | using Xunit;
6 |
7 | namespace Poltergeist.Core.Tests
8 | {
9 | public unsafe class NativeArrayTests
10 | {
11 | [Theory]
12 | [InlineData(0)]
13 | [InlineData(1)]
14 | [InlineData(127)]
15 | [InlineData(1024)]
16 | public void CreateTest(int size)
17 | {
18 | using (NativeArray nativeArray = new(size))
19 | {
20 | Assert.True(nativeArray.Data != null);
21 | Assert.NotNull(nativeArray.Allocator);
22 | Assert.Equal(size, nativeArray.Count);
23 | Assert.Equal(size * sizeof(int), nativeArray.ByteSize);
24 | Assert.True(nativeArray.AlignedSize >= nativeArray.ByteSize);
25 | // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
26 | Assert.True(nativeArray.All(v => v == 0));
27 | }
28 | }
29 |
30 | [Theory]
31 | [InlineData(0)]
32 | [InlineData(1)]
33 | [InlineData(127)]
34 | [InlineData(1024)]
35 | public void CreateWithDataTest(int size)
36 | {
37 | int[] array = new int[size];
38 | new Random().NextBytes(MemoryMarshal.AsBytes(array.AsSpan()));
39 | using (NativeArray nativeArray = new(array))
40 | {
41 | Assert.True(nativeArray.Data != null);
42 | Assert.NotNull(nativeArray.Allocator);
43 | Assert.Equal(array.Length, nativeArray.Count);
44 | Assert.Equal(array.Length * sizeof(int), nativeArray.ByteSize);
45 | Assert.True(nativeArray.AlignedSize >= nativeArray.ByteSize);
46 | AssertUtils.SequenceEqual(array, nativeArray);
47 | }
48 | }
49 |
50 | [Theory]
51 | [InlineData(-1)]
52 | [InlineData(int.MinValue)]
53 | [InlineData(int.MaxValue)]
54 | public void InvalidLengthTest(int length)
55 | {
56 | Assert.Throws(() =>
57 | {
58 | using (NativeArray _ = new(length)) { }
59 | });
60 | }
61 |
62 | [Fact]
63 | public void SpanTest()
64 | {
65 | using (NativeArray nativeArray = new(1024))
66 | {
67 | Assert.Equal(nativeArray.Count, nativeArray.AsSpan().Length);
68 | Assert.Equal(nativeArray.Count, nativeArray.AsReadOnlySpan().Length);
69 | AssertUtils.SequenceEqual(nativeArray.AsSpan(), new ReadOnlySpan(nativeArray.Data, nativeArray.Count));
70 | AssertUtils.SequenceEqual(nativeArray.AsReadOnlySpan(), new ReadOnlySpan(nativeArray.Data, nativeArray.Count));
71 | }
72 | }
73 |
74 | [Theory]
75 | [InlineData(0)]
76 | [InlineData(-1)]
77 | [InlineData(int.MinValue)]
78 | [InlineData(int.MaxValue)]
79 | public void FillTest(int value)
80 | {
81 | using (NativeArray nativeArray = new(1024, false))
82 | {
83 | nativeArray.Fill(value);
84 | // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
85 | Assert.True(nativeArray.All(v => v == value));
86 | }
87 | }
88 |
89 | [Fact]
90 | public void ClearTest()
91 | {
92 | using (NativeArray nativeArray = new(1024, false))
93 | {
94 | nativeArray.Fill(uint.MaxValue);
95 | nativeArray.Clear();
96 | // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
97 | Assert.True(nativeArray.All(v => v == 0));
98 | }
99 | }
100 |
101 | [Theory]
102 | [InlineData(0)]
103 | [InlineData(1)]
104 | [InlineData(128)]
105 | public void EnumerableTest(int length)
106 | {
107 | using (NativeArray nativeArray = new(length))
108 | {
109 | nativeArray.Fill(uint.MaxValue);
110 | // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
111 | Assert.True(nativeArray.All(v => v == uint.MaxValue));
112 | #pragma warning disable CA1826
113 | Assert.Equal(nativeArray.Count, nativeArray.Count());
114 | #pragma warning restore CA1826
115 | }
116 | }
117 |
118 | [Theory]
119 | [InlineData(0)]
120 | [InlineData(1)]
121 | [InlineData(128)]
122 | public void ToArrayTest(int length)
123 | {
124 | using (NativeArray nativeArray = new(length))
125 | {
126 | nativeArray.Fill(uint.MaxValue);
127 | AssertUtils.SequenceEqual(nativeArray, nativeArray.ToArray());
128 | }
129 | }
130 |
131 | [Theory]
132 | [InlineData(0)]
133 | [InlineData(1)]
134 | [InlineData(128)]
135 | public void IterationTest(int length)
136 | {
137 | using (NativeArray nativeArray = new(length))
138 | {
139 | for (int i = 0; i < nativeArray.Count; i++)
140 | nativeArray[i] = uint.MaxValue;
141 | for (int i = 0; i < nativeArray.Count; i++)
142 | Assert.Equal(uint.MaxValue, nativeArray[i]);
143 | for (uint i = 0; i < nativeArray.Count; i++)
144 | Assert.Equal(uint.MaxValue, nativeArray[i]);
145 | foreach (uint u in nativeArray)
146 | Assert.Equal(uint.MaxValue, u);
147 | }
148 | }
149 |
150 | [Theory]
151 | [InlineData(0)]
152 | [InlineData(1)]
153 | [InlineData(128)]
154 | public void IndexerTest(int length)
155 | {
156 | using (NativeArray nativeArray = new(length))
157 | {
158 | Assert.Throws(() => { _ = nativeArray[-1]; });
159 | Assert.Throws(() => { _ = nativeArray[nativeArray.Count]; });
160 | if (nativeArray.Count != 0)
161 | {
162 | _ = nativeArray[0];
163 | _ = nativeArray[nativeArray.Count - 1];
164 | }
165 | Assert.Throws(() => { _ = nativeArray[(uint)nativeArray.Count]; });
166 | if (nativeArray.Count != 0)
167 | {
168 | _ = nativeArray[0u];
169 | _ = nativeArray[(uint)(nativeArray.Count - 1)];
170 | }
171 | }
172 | }
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/Poltergeist.Core.Tests/NativeListTests.cs:
--------------------------------------------------------------------------------
1 | using Poltergeist.Core.Collections;
2 | using Poltergeist.Core.Memory;
3 | using System;
4 | using System.Collections;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Runtime.InteropServices;
8 | using System.Security.Cryptography;
9 | using Xunit;
10 |
11 | // ReSharper disable HeapView.BoxingAllocation
12 | // ReSharper disable ParameterOnlyUsedForPreconditionCheck.Local
13 | // ReSharper disable UseCollectionCountProperty
14 |
15 | namespace Poltergeist.Core.Tests
16 | {
17 | public unsafe class NativeListTests
18 | {
19 | [Fact]
20 | public void CreateTest()
21 | {
22 | using (NativeList nativeList = new())
23 | {
24 | Assert.NotNull(nativeList.Allocator);
25 | Assert.Equal(NativeList.DefaultAllocator, nativeList.Allocator);
26 | Assert.True(nativeList.Data != null);
27 | Assert.Empty(nativeList);
28 | Assert.True(nativeList.Capacity > 0);
29 | Assert.False(nativeList.IsFixedSize);
30 | Assert.False(nativeList.IsReadOnly);
31 | Assert.False(nativeList.IsSynchronized);
32 | }
33 | }
34 |
35 | [Theory]
36 | [InlineData(0)]
37 | [InlineData(1)]
38 | [InlineData(1024)]
39 | public void CreateDataTest(int size)
40 | {
41 | int[] data = new int[size];
42 | RandomNumberGenerator.Fill(MemoryMarshal.AsBytes(data.AsSpan()));
43 | using (NativeList nativeList = new(data))
44 | {
45 | Assert.NotNull(nativeList.Allocator);
46 | Assert.Equal(NativeList.DefaultAllocator, nativeList.Allocator);
47 | Assert.True(nativeList.Data != null);
48 | AssertUtils.SequenceEqual(data, nativeList);
49 | Assert.True(nativeList.Capacity >= data.Length);
50 | Assert.False(nativeList.IsFixedSize);
51 | Assert.False(nativeList.IsReadOnly);
52 | Assert.False(nativeList.IsSynchronized);
53 | }
54 | }
55 |
56 | [Theory]
57 | [InlineData(1, 0)]
58 | [InlineData(128, 1024)]
59 | [InlineData(1024, 128)]
60 | public void EnsureCapacityTest(int initial, int requested)
61 | {
62 | using (NativeList nativeList = new(initial))
63 | {
64 | Assert.True(nativeList.Capacity >= initial);
65 | nativeList.EnsureCapacity(requested);
66 | Assert.True(nativeList.Capacity >= requested);
67 | }
68 | }
69 |
70 | [Fact]
71 | public void EnsureCapacityDataTest()
72 | {
73 | int[] data = new int[1024];
74 | RandomNumberGenerator.Fill(MemoryMarshal.AsBytes(data.AsSpan()));
75 | using (NativeList nativeList = new(data))
76 | {
77 | AssertUtils.SequenceEqual(data, nativeList);
78 | nativeList.EnsureCapacity(nativeList.Count * 3);
79 | AssertUtils.SequenceEqual(data, nativeList);
80 | }
81 | }
82 |
83 | [Theory]
84 | [InlineData(0)]
85 | [InlineData(1)]
86 | [InlineData(128)]
87 | public void IterationTest(int length)
88 | {
89 | uint[] array = new uint[length];
90 | array.AsSpan().Fill(uint.MaxValue);
91 | using (NativeList nativeList = new(array))
92 | {
93 | for (int i = 0; i < nativeList.Count; i++)
94 | Assert.Equal(uint.MaxValue, nativeList[i]);
95 | for (uint i = 0; i < nativeList.Count; i++)
96 | Assert.Equal(uint.MaxValue, nativeList[i]);
97 | foreach (uint u in nativeList)
98 | Assert.Equal(uint.MaxValue, u);
99 | }
100 | }
101 |
102 | [Theory]
103 | [InlineData(0)]
104 | [InlineData(1)]
105 | [InlineData(128)]
106 | public void IndexerTest(int length)
107 | {
108 | using (NativeList nativeList = new(new uint[length]))
109 | {
110 | Assert.Throws(() => { _ = nativeList[-1]; });
111 | Assert.Throws(() => { _ = nativeList[nativeList.Count]; });
112 | if (nativeList.Count != 0)
113 | {
114 | _ = nativeList[0];
115 | // ReSharper disable once UseIndexFromEndExpression
116 | _ = nativeList[nativeList.Count - 1];
117 | _ = nativeList[^1];
118 | }
119 | Assert.Throws(() => { _ = nativeList[(uint)nativeList.Count]; });
120 | if (nativeList.Count != 0)
121 | {
122 | _ = nativeList[0u];
123 | _ = nativeList[(uint)(nativeList.Count - 1)];
124 | }
125 | }
126 | }
127 |
128 | [Theory]
129 | [InlineData(new int[0], 512)]
130 | [InlineData(new[] { 256 }, 512)]
131 | [InlineData(new[] { 256, 512, 512 }, 512)]
132 | public void AddTest(int[] initial, int value)
133 | {
134 | using (NativeList nativeList = new(initial))
135 | {
136 | List expected = new(initial);
137 | AssertUtils.SequenceEqual(initial, nativeList);
138 | nativeList.Add(value);
139 | expected.Add(value);
140 | AssertUtils.SequenceEqual(expected, nativeList);
141 | }
142 | using (NativeList nativeList = new(initial))
143 | {
144 | List expected = new(initial);
145 | AssertUtils.SequenceEqual(initial, nativeList);
146 | nativeList.Add((object)value);
147 | ((IList)expected).Add(value);
148 | AssertUtils.SequenceEqual(expected, nativeList);
149 | }
150 | }
151 |
152 | [Theory]
153 | [InlineData(new int[0], new[] { 512 })]
154 | [InlineData(new[] { 256 }, new[] { 512 })]
155 | [InlineData(new[] { 256, 512, 512 }, new[] { 512 })]
156 | [InlineData(new int[0], new[] { 128, 512 })]
157 | [InlineData(new[] { 256 }, new[] { 128, 512 })]
158 | [InlineData(new[] { 256, 512, 512 }, new[] { 128, 512 })]
159 | [InlineData(new[] { 256, 512, 512, 64, 32, 1024, 16384, 32768, 2, 4, 16 }, new[] { 128, 512, 2048, 4096, 8 })]
160 | public void AddRangeTest(int[] initial, int[] values)
161 | {
162 | using (NativeList nativeList = new(initial))
163 | {
164 | List expected = new(initial);
165 | AssertUtils.SequenceEqual(initial, nativeList);
166 | nativeList.AddRange(values);
167 | expected.AddRange(values);
168 | AssertUtils.SequenceEqual(expected, nativeList);
169 | }
170 | }
171 |
172 | [Theory]
173 | [InlineData(new int[0], 512, 0)]
174 | [InlineData(new[] { 256 }, 512, 0)]
175 | [InlineData(new[] { 256, 512, 512 }, 512, 0)]
176 | public void InsertTest(int[] initial, int value, int index)
177 | {
178 | using (NativeList nativeList = new(initial))
179 | {
180 | List expected = new(initial);
181 | AssertUtils.SequenceEqual(initial, nativeList);
182 | nativeList.Insert(index, value);
183 | expected.Insert(index, value);
184 | AssertUtils.SequenceEqual(expected, nativeList);
185 | }
186 | using (NativeList nativeList = new(initial))
187 | {
188 | List expected = new(initial);
189 | AssertUtils.SequenceEqual(initial, nativeList);
190 | nativeList.Insert(index, (object)value);
191 | ((IList)expected).Insert(index, value);
192 | AssertUtils.SequenceEqual(expected, nativeList);
193 | }
194 | }
195 |
196 | [Theory]
197 | [InlineData(new int[0], new[] { 512 }, 0)]
198 | [InlineData(new[] { 256 }, new[] { 512 }, 0)]
199 | [InlineData(new[] { 256, 512, 512 }, new[] { 512 }, 0)]
200 | [InlineData(new int[0], new[] { 128, 512 }, 0)]
201 | [InlineData(new[] { 256 }, new[] { 128, 512 }, 0)]
202 | [InlineData(new[] { 256, 512, 512 }, new[] { 128, 512 }, 0)]
203 | [InlineData(new[] { 256, 512, 512, 64, 32, 1024, 16384, 32768, 2, 4, 16 }, new[] { 128, 512, 2048, 4096, 8 }, 0)]
204 | public void InsertRangeTest(int[] initial, int[] values, int index)
205 | {
206 | using (NativeList nativeList = new(initial))
207 | {
208 | List expected = new(initial);
209 | AssertUtils.SequenceEqual(initial, nativeList);
210 | nativeList.InsertRange(index, values);
211 | expected.InsertRange(index, values);
212 | AssertUtils.SequenceEqual(expected, nativeList);
213 | }
214 | }
215 |
216 | [Theory]
217 | [InlineData(new[] { 256 }, 0)]
218 | [InlineData(new[] { 256, 512, 1024 }, 0)]
219 | [InlineData(new[] { 256, 512, 1024 }, 1)]
220 | [InlineData(new[] { 256, 512, 1024 }, 2)]
221 | public void RemoveAtTest(int[] initial, int index)
222 | {
223 | using (NativeList nativeList = new(initial))
224 | {
225 | List expected = new(initial);
226 | AssertUtils.SequenceEqual(initial, nativeList);
227 | nativeList.RemoveAt(index);
228 | expected.RemoveAt(index);
229 | AssertUtils.SequenceEqual(expected, nativeList);
230 | }
231 | }
232 |
233 | [Theory]
234 | [InlineData(new[] { 256, 512, 512, 128, 512 }, 3, 1)]
235 | [InlineData(new[] { 256, 512, 512, 128, 512 }, 0, 1)]
236 | [InlineData(new[] { 256, 512, 512, 128, 512 }, 3, 2)]
237 | [InlineData(new[] { 256, 512, 512, 128, 512 }, 0, 2)]
238 | [InlineData(new[] { 256, 512, 512, 128, 512 }, 3, 0)]
239 | [InlineData(new[] { 256, 512, 512, 128, 512 }, 0, 0)]
240 | [InlineData(new[] { 256, 512, 512, 64, 32, 1024, 16384, 32768, 2, 4, 16, 128, 512, 2048, 4096, 8 }, 5, 5)]
241 | public void RemoveRangeTest(int[] initial, int index, int count)
242 | {
243 | using (NativeList nativeList = new(initial))
244 | {
245 | List expected = new(initial);
246 | AssertUtils.SequenceEqual(initial, nativeList);
247 | nativeList.RemoveRange(index, count);
248 | expected.RemoveRange(index, count);
249 | AssertUtils.SequenceEqual(expected, nativeList);
250 | }
251 | }
252 |
253 | [Theory]
254 | [InlineData(new[] { 256 }, 256)]
255 | [InlineData(new[] { 256, 512, 1024 }, 256)]
256 | [InlineData(new[] { 256, 512, 1024 }, 512)]
257 | [InlineData(new[] { 256, 512, 1024 }, 1024)]
258 | public void RemoveTest(int[] initial, int value)
259 | {
260 | using (NativeList nativeList = new(initial))
261 | {
262 | List expected = new(initial);
263 | AssertUtils.SequenceEqual(initial, nativeList);
264 | nativeList.Remove(value);
265 | expected.Remove(value);
266 | AssertUtils.SequenceEqual(expected, nativeList);
267 | }
268 | using (NativeList nativeList = new(initial))
269 | {
270 | List expected = new(initial);
271 | AssertUtils.SequenceEqual(initial, nativeList);
272 | nativeList.Remove((object)value);
273 | ((IList)expected).Remove(value);
274 | AssertUtils.SequenceEqual(expected, nativeList);
275 | }
276 | }
277 |
278 | [Theory]
279 | [InlineData(new int[0])]
280 | [InlineData(new[] { 256 })]
281 | [InlineData(new[] { 256, 512 })]
282 | [InlineData(new[] { 256, 512, 1024 })]
283 | public void ClearTest(int[] initial)
284 | {
285 | using (NativeList nativeList = new(initial))
286 | {
287 | AssertUtils.SequenceEqual(initial, nativeList);
288 | nativeList.Clear();
289 | AssertUtils.SequenceEqual(Array.Empty(), nativeList);
290 | }
291 | }
292 |
293 | [Theory]
294 | [InlineData(new int[0], 512)]
295 | [InlineData(new[] { 256 }, 256)]
296 | [InlineData(new[] { 256, 512 }, 256)]
297 | [InlineData(new[] { 256, 512 }, 512)]
298 | [InlineData(new[] { 256, 512, 1024 }, 512)]
299 | [InlineData(new[] { 256, 512, 1024 }, 1024)]
300 | [InlineData(new[] { 256, 512, 1024 }, 128)]
301 | [InlineData(new[] { 256, 512, 512, 512, 1024 }, 512)]
302 | [InlineData(new[] { 256, 1024, 512, 1024 }, 1024)]
303 | [InlineData(new[] { 128, 256, 512, 1024, 128 }, 128)]
304 | public void IndefOfContainsTest(int[] initial, int value)
305 | {
306 | using (NativeList nativeList = new(initial))
307 | {
308 | List expected = new(initial);
309 | AssertUtils.SequenceEqual(initial, nativeList);
310 | Assert.Equal(expected.IndexOf(value), nativeList.IndexOf(value));
311 | Assert.Equal(((IList)expected).IndexOf(value), nativeList.IndexOf((object)value));
312 | Assert.Equal(expected.LastIndexOf(value), nativeList.LastIndexOf(value));
313 | Assert.Equal(expected.Contains(value), nativeList.Contains(value));
314 | Assert.Equal(((IList)expected).Contains(value), nativeList.Contains((object)value));
315 | }
316 | }
317 |
318 | [Fact]
319 | public void SpanTest()
320 | {
321 | uint[] array = new uint[1024];
322 | array.AsSpan().Fill(uint.MaxValue);
323 | using (NativeList nativeList = new(array))
324 | {
325 | Assert.Equal(nativeList.Count, nativeList.AsSpan().Length);
326 | Assert.Equal(nativeList.Count, nativeList.AsReadOnlySpan().Length);
327 | AssertUtils.SequenceEqual(nativeList.AsSpan(), new ReadOnlySpan(nativeList.Data, nativeList.Count));
328 | AssertUtils.SequenceEqual(nativeList.AsReadOnlySpan(), new ReadOnlySpan(nativeList.Data, nativeList.Count));
329 | }
330 | }
331 |
332 | [Theory]
333 | [InlineData(0)]
334 | [InlineData(1)]
335 | [InlineData(128)]
336 | public void ToArrayListTest(int length)
337 | {
338 | uint[] array = new uint[length];
339 | array.AsSpan().Fill(uint.MaxValue);
340 | using (NativeList nativeList = new(array))
341 | {
342 | AssertUtils.SequenceEqual(array, nativeList);
343 | AssertUtils.SequenceEqual(nativeList, nativeList.ToArray());
344 | AssertUtils.SequenceEqual(nativeList, nativeList.ToList());
345 | }
346 | }
347 |
348 | [Theory]
349 | [InlineData(0)]
350 | [InlineData(1)]
351 | [InlineData(128)]
352 | public void EnumerableTest(int length)
353 | {
354 | uint[] array = new uint[length];
355 | array.AsSpan().Fill(uint.MaxValue);
356 | using (NativeList nativeList = new(array))
357 | {
358 | Assert.True(nativeList.All(v => v == uint.MaxValue));
359 | Assert.Equal(nativeList.Count, nativeList.Count());
360 | }
361 | }
362 |
363 | [Theory]
364 | [InlineData(0)]
365 | [InlineData(1)]
366 | [InlineData(128)]
367 | public void CopyToTest(int length)
368 | {
369 | uint[] array = new uint[length];
370 | array.AsSpan().Fill(uint.MaxValue);
371 | using (NativeList nativeList = new(array))
372 | {
373 | uint[] array1 = new uint[nativeList.Count];
374 | nativeList.CopyTo(array1);
375 | AssertUtils.SequenceEqual(nativeList, array1);
376 | uint[] array2 = new uint[nativeList.Count];
377 | nativeList.CopyTo(array2, 0);
378 | AssertUtils.SequenceEqual(nativeList, array2);
379 | Array array3 = new uint[nativeList.Count];
380 | nativeList.CopyTo(array3, 0);
381 | AssertUtils.SequenceEqual(nativeList, array3.ToArray());
382 | // ReSharper disable once UseArrayCreationExpression.1
383 | Array array4 = Array.CreateInstance(typeof(uint), nativeList.Count);
384 | nativeList.CopyTo(array4, 0);
385 | AssertUtils.SequenceEqual(nativeList, array4.ToArray());
386 | Array array5 = Array.CreateInstance(typeof(uint), new[] { nativeList.Count }, new[] { 0 });
387 | nativeList.CopyTo(array5, 0);
388 | AssertUtils.SequenceEqual(nativeList, array5.ToArray());
389 | uint[] array6 = new uint[nativeList.Count];
390 | Assert.True(nativeList.TryCopyTo(array6));
391 | AssertUtils.SequenceEqual(nativeList, array6);
392 | uint[] array7 = new uint[nativeList.Count / 2];
393 | Assert.Equal(nativeList.Count <= array7.Length, nativeList.TryCopyTo(array7));
394 | }
395 | }
396 | }
397 | }
398 |
--------------------------------------------------------------------------------
/Poltergeist.Core.Tests/NativeStructTests.cs:
--------------------------------------------------------------------------------
1 | using Poltergeist.Core.Memory;
2 | using System;
3 | using System.Runtime.InteropServices;
4 | using Xunit;
5 |
6 | namespace Poltergeist.Core.Tests
7 | {
8 | public class NativeStructTests
9 | {
10 | [Theory]
11 | [InlineData(0, null, null, 0)]
12 | [InlineData(int.MaxValue, "abcde", "fghij", int.MaxValue)]
13 | [InlineData(-1, "abc\tde", "fgh\tij", -1)]
14 | public unsafe void DataTest(int integer, string utf8String, string wideString, int ptr)
15 | {
16 | TestStruct testStruct = new() { integer = integer, utf8String = utf8String, wideString = wideString, ptr = new IntPtr(ptr) };
17 | using (NativeStruct nativeStruct = new(testStruct))
18 | {
19 | Assert.True(nativeStruct.Data != null);
20 | Assert.NotNull(nativeStruct.Allocator);
21 | Assert.Equal(NativeStruct.DefaultAllocator, nativeStruct.Allocator);
22 | Assert.Equal(Marshal.SizeOf(), nativeStruct.Size);
23 | Assert.Equal(testStruct.integer, *(int*)nativeStruct.Data);
24 | Assert.Equal(testStruct, Marshal.PtrToStructure(new IntPtr(nativeStruct.Data)));
25 | }
26 | }
27 |
28 | [StructLayout(LayoutKind.Sequential)]
29 | private struct TestStruct : IEquatable
30 | {
31 | public int integer;
32 | [MarshalAs(UnmanagedType.LPUTF8Str)]
33 | public string utf8String;
34 | [MarshalAs(UnmanagedType.LPWStr)]
35 | public string wideString;
36 | public IntPtr ptr;
37 |
38 | public bool Equals(TestStruct other)
39 | {
40 | return integer == other.integer && utf8String == other.utf8String && wideString == other.wideString && ptr == other.ptr;
41 | }
42 |
43 | public override bool Equals(object obj)
44 | {
45 | return obj is TestStruct other && Equals(other);
46 | }
47 |
48 | public override int GetHashCode()
49 | {
50 | // ReSharper disable NonReadonlyMemberInGetHashCode
51 | return HashCode.Combine(integer, utf8String, wideString, ptr);
52 | // ReSharper restore NonReadonlyMemberInGetHashCode
53 | }
54 |
55 | public static bool operator ==(TestStruct left, TestStruct right)
56 | {
57 | return left.Equals(right);
58 | }
59 |
60 | public static bool operator !=(TestStruct left, TestStruct right)
61 | {
62 | return !left.Equals(right);
63 | }
64 |
65 | public override string ToString()
66 | {
67 | // ReSharper disable HeapView.BoxingAllocation
68 | return $"({integer}) ({utf8String}) ({wideString}) ({ptr})";
69 | // ReSharper restore HeapView.BoxingAllocation
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Poltergeist.Core.Tests/PointerUtilsTests.cs:
--------------------------------------------------------------------------------
1 | using Poltergeist.Core.Memory;
2 | using System;
3 | using Xunit;
4 |
5 | namespace Poltergeist.Core.Tests
6 | {
7 | public unsafe class PointerUtilsTests
8 | {
9 | [Fact]
10 | public void NullTest()
11 | {
12 | Assert.False(PointerUtils.IsReadable((byte*)null));
13 | }
14 |
15 | [Fact]
16 | public void NullDoubleTest()
17 | {
18 | Assert.False(PointerUtils.IsReadable((byte**)null));
19 | }
20 |
21 | [Fact]
22 | public void ValidTest()
23 | {
24 | using (NativeArray nativeArray = new(1))
25 | Assert.True(PointerUtils.IsReadable(nativeArray.Data));
26 | }
27 |
28 | [Fact]
29 | public void ValidDoubleTest()
30 | {
31 | using (NativeArray nativeArray = new(1))
32 | Assert.True(PointerUtils.IsReadable((byte**)nativeArray.Data));
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Poltergeist.Core.Tests/Poltergeist.Core.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net5.0
5 | 9
6 | true
7 | false
8 |
9 |
10 |
11 |
12 |
13 |
14 | runtime; build; native; contentfiles; analyzers; buildtransitive
15 | all
16 |
17 |
18 | runtime; build; native; contentfiles; analyzers; buildtransitive
19 | all
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Poltergeist.Core.Tests/StringExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | using Poltergeist.Core.Extensions;
2 | using System;
3 | using Xunit;
4 | using Xunit.Abstractions;
5 |
6 | namespace Poltergeist.Core.Tests
7 | {
8 | public class StringExtensionsTests
9 | {
10 | [Theory]
11 | [InlineData("", new[] { "" })]
12 | [InlineData("ab", new[] { "ab" })]
13 | [InlineData("a\rb", new[] { "a", "b" })]
14 | [InlineData("a\nb", new[] { "a", "b" })]
15 | [InlineData("a\r\nb", new[] { "a", "b" })]
16 | [InlineData("\r", new[] { "", "" })]
17 | [InlineData("ab\r", new[] { "ab", "" })]
18 | [InlineData("\n", new[] { "", "" })]
19 | [InlineData("ab\n", new[] { "ab", "" })]
20 | [InlineData("\r\n", new[] { "", "" })]
21 | [InlineData("ab\r\n", new[] { "ab", "" })]
22 | [InlineData("a\rb\nc", new[] { "a", "b", "c" })]
23 | [InlineData("a\nb\rc", new[] { "a", "b", "c" })]
24 | [InlineData("a\r\nb\rc\nd", new[] { "a", "b", "c", "d" })]
25 | [InlineData("a\rb\nc\r\n", new[] { "a", "b", "c", "" })]
26 | [InlineData("a\nb\rc\r\n", new[] { "a", "b", "c", "" })]
27 | [InlineData("a\r\nb\rc\nd\r\n", new[] { "a", "b", "c", "d", "" })]
28 | public void SplitTest(string value, string[] expected)
29 | {
30 | int i = 0;
31 | foreach (ReadOnlySpan line in value.AsSpan().SplitLines())
32 | {
33 | Assert.True(line.Equals(expected[i], StringComparison.Ordinal), $"\"{expected[i]}\" == \"{line.ToString()}\"");
34 | i++;
35 | }
36 | Assert.Equal(expected.Length, i);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Poltergeist.Core/Collections/EnumerableUtils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace Poltergeist.Core.Collections
5 | {
6 | public static class EnumerableUtils
7 | {
8 | public static IEnumerable Join(params IEnumerable[] enumerables)
9 | {
10 | foreach (IEnumerable enumerable in enumerables)
11 | foreach (T element in enumerable)
12 | yield return element;
13 | }
14 |
15 | public static T[] ToArray(this Array array)
16 | {
17 | T[] result = new T[array.Length];
18 | array.CopyTo(result, 0);
19 | return result;
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Poltergeist.Core/ExitHandler.cs:
--------------------------------------------------------------------------------
1 | using Mono.Unix;
2 | using Mono.Unix.Native;
3 | using System;
4 | using System.Diagnostics;
5 | using System.Runtime.InteropServices;
6 | using System.Threading;
7 |
8 | namespace Poltergeist.Core
9 | {
10 | public static class ExitHandler
11 | {
12 | public static event Action Exit;
13 |
14 | private static readonly object ExitLock = new();
15 | private static readonly HandlerRoutine Routine = OnNativeSignal;
16 |
17 | private static Process _currentProcess;
18 | private static bool _called;
19 |
20 | static ExitHandler()
21 | {
22 | _currentProcess = Process.GetCurrentProcess();
23 | _currentProcess.EnableRaisingEvents = true;
24 | _currentProcess.Exited += (_, _) => CallExit();
25 | AppDomain.CurrentDomain.ProcessExit += (_, _) => CallExit();
26 | AppDomain.CurrentDomain.DomainUnload += (_, _) => CallExit();
27 |
28 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
29 | {
30 | SetConsoleCtrlHandler(Routine, true);
31 | }
32 | else
33 | {
34 | new Thread(() =>
35 | {
36 | UnixSignal.WaitAny(new UnixSignal[]{
37 | new(Signum.SIGINT),
38 | new(Signum.SIGTERM),
39 | new(Signum.SIGKILL),
40 | new(Signum.SIGUSR1),
41 | new(Signum.SIGUSR2),
42 | new(Signum.SIGHUP)
43 | }, -1);
44 | CallExit();
45 | })
46 | { IsBackground = true }.Start();
47 | }
48 | }
49 |
50 | private static void CallExit()
51 | {
52 | lock (ExitLock)
53 | {
54 | if (_called)
55 | return;
56 |
57 | _called = true;
58 | _currentProcess?.Dispose();
59 | _currentProcess = null;
60 | Exit?.Invoke();
61 | }
62 | }
63 |
64 | public static void ExitProcess(int code = 0)
65 | {
66 | lock (ExitLock)
67 | {
68 | CallExit();
69 | Environment.Exit(code);
70 | }
71 | }
72 |
73 | private static bool OnNativeSignal(CtrlTypes ctrl)
74 | {
75 | CallExit();
76 | return true;
77 | }
78 |
79 | [DllImport("Kernel32", EntryPoint = "SetConsoleCtrlHandler")]
80 | private static extern bool SetConsoleCtrlHandler(HandlerRoutine handler, bool add);
81 |
82 | [UnmanagedFunctionPointer(CallingConvention.Winapi)]
83 | private delegate bool HandlerRoutine(CtrlTypes ctrlType);
84 |
85 | private enum CtrlTypes
86 | {
87 | // ReSharper disable UnusedMember.Local
88 | CtrlCEvent,
89 | CtrlBreakEvent,
90 | CtrlCloseEvent,
91 | CtrlLogoffEvent,
92 | CtrlShutdownEvent
93 | // ReSharper restore UnusedMember.Local
94 | }
95 |
96 | public sealed class LifetimeHandle : IDisposable
97 | {
98 | public void Dispose()
99 | {
100 | CallExit();
101 | GC.SuppressFinalize(this);
102 | }
103 |
104 | ~LifetimeHandle()
105 | {
106 | CallExit();
107 | }
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/Poltergeist.Core/Extensions/StringExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Poltergeist.Core.Extensions
4 | {
5 | public static class StringExtensions
6 | {
7 | public static LineSplitEnumerator SplitLines(this ReadOnlySpan str)
8 | {
9 | return new(str);
10 | }
11 |
12 | public ref struct LineSplitEnumerator
13 | {
14 | private ReadOnlySpan _data;
15 | private bool _endOnEmpty;
16 |
17 | public ReadOnlySpan Current { get; private set; }
18 |
19 | public LineSplitEnumerator(ReadOnlySpan data)
20 | {
21 | _data = data;
22 | _endOnEmpty = false;
23 | Current = default;
24 | }
25 |
26 | public bool MoveNext()
27 | {
28 | if (_data.Length == 0)
29 | {
30 | if (_endOnEmpty)
31 | return false;
32 | Current = ReadOnlySpan.Empty;
33 | _endOnEmpty = true;
34 | return true;
35 | }
36 |
37 | int index = _data.IndexOfAny('\r', '\n');
38 | if (index == -1)
39 | {
40 | Current = _data;
41 | _data = ReadOnlySpan.Empty;
42 | _endOnEmpty = true;
43 | return true;
44 | }
45 |
46 | Current = _data.Slice(0, index);
47 | _data = _data.Slice(index + (_data[index] == '\r' && index < _data.Length - 1 && _data[index + 1] == '\n' ? 2 : 1));
48 | _endOnEmpty = false;
49 | return true;
50 | }
51 |
52 | public LineSplitEnumerator GetEnumerator() => this;
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Poltergeist.Core/IO/BetterConsole.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Runtime.InteropServices;
4 | using System.Text;
5 | using System.Threading;
6 |
7 | namespace Poltergeist.Core.IO
8 | {
9 | public static unsafe class BetterConsole
10 | {
11 | private static readonly ConcurrentQueue> Lines = new();
12 | private static readonly StringBuilder Buffer = new(16384);
13 | private static readonly AutoResetEvent Wait = new(false);
14 | private static readonly Thread WriteThread;
15 | private static readonly IntPtr OutHandle;
16 |
17 | private static bool _exit;
18 | private static bool _paused;
19 |
20 | static BetterConsole()
21 | {
22 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !Console.IsOutputRedirected)
23 | OutHandle = GetStdHandle(-11);
24 | ExitHandler.Exit += () =>
25 | {
26 | _exit = true;
27 | if (WriteThread == null || !WriteThread.IsAlive)
28 | return;
29 | Wait.Set();
30 | WriteThread.Join();
31 | };
32 | WriteThread = new Thread(WriteLoop)
33 | {
34 | IsBackground = true
35 | };
36 | WriteThread.Start();
37 | }
38 |
39 | public static void WriteLine(string line) => Lines.Enqueue(line.AsMemory());
40 |
41 | public static void WriteLine(ReadOnlyMemory line) => Lines.Enqueue(line);
42 |
43 | public static void Flush() => Wait.Set();
44 |
45 | public static void BeginConsoleUpdate() => _paused = true;
46 |
47 | public static void EndConsoleUpdate() => _paused = false;
48 |
49 | private static void WriteLoop()
50 | {
51 | while (true)
52 | {
53 | Wait.WaitOne(100);
54 |
55 | if (_paused && !_exit)
56 | continue;
57 |
58 | while (Lines.TryDequeue(out ReadOnlyMemory line))
59 | {
60 | Buffer.Append(line);
61 | Buffer.AppendLine();
62 | }
63 |
64 | if (Buffer.Length == 0)
65 | {
66 | if (_exit)
67 | return;
68 | continue;
69 | }
70 |
71 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && OutHandle != IntPtr.Zero)
72 | {
73 | foreach (ReadOnlyMemory chunk in Buffer.GetChunks())
74 | {
75 | ReadOnlySpan span = chunk.Span;
76 | fixed (char* ptr = span)
77 | WriteConsole(OutHandle, ptr, (uint)span.Length, out _);
78 | }
79 | }
80 | else
81 | {
82 | Console.Write(Buffer.ToString());
83 | }
84 |
85 | Buffer.Clear();
86 |
87 | if (_exit)
88 | return;
89 | }
90 | }
91 |
92 | [DllImport("Kernel32", EntryPoint = "GetStdHandle", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
93 | private static extern IntPtr GetStdHandle(int handle);
94 |
95 | [DllImport("Kernel32", EntryPoint = "WriteConsoleW", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
96 | private static extern bool WriteConsole(IntPtr handle, char* buffer, uint count, out uint written,
97 | void* reserved = null);
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/Poltergeist.Core/Math/EnumUtils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 |
4 | namespace Poltergeist.Core.Math
5 | {
6 | public static class EnumUtils where T : unmanaged, Enum
7 | {
8 | private static readonly T[] ValuesCache = Enum.GetValues();
9 | private static readonly string[] NamesCache = Enum.GetNames();
10 |
11 | public static ReadOnlySpan Values => ValuesCache;
12 | public static ReadOnlySpan Names => NamesCache;
13 | public static Type UnderlyingType { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } = Enum.GetUnderlyingType(typeof(T));
14 |
15 | #pragma warning disable CA2248
16 | // ReSharper disable once HeapView.PossibleBoxingAllocation
17 | // ReSharper disable once HeapView.BoxingAllocation
18 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
19 | public static bool HasFlag(T value, T flag) => value.HasFlag(flag);
20 | #pragma warning restore CA2248
21 | }
22 |
23 | public static class EnumUtils
24 | {
25 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
26 | public static bool HasFlagF(this T value, T flag) where T : unmanaged, Enum => EnumUtils.HasFlag(value, flag);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Poltergeist.Core/Math/MathUtils.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace Poltergeist.Core.Math
4 | {
5 | public static class MathUtils
6 | {
7 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
8 | public static int Align(int value, int alignment) => (value + alignment - 1) / alignment * alignment;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Poltergeist.Core/Math/Vector2.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | namespace Poltergeist.Core.Math
6 | {
7 | [StructLayout(LayoutKind.Sequential)]
8 | public readonly struct Vector2 : IEquatable, IFormattable
9 | {
10 | private const float Epsilon = 0.001f;
11 |
12 | public static Vector2 Zero => new(0f, 0f);
13 | public static Vector2 One => new(1f, 1f);
14 | public static Vector2 Up => new(0f, 1f);
15 | public static Vector2 Down => new(0f, -1f);
16 | public static Vector2 Left => new(-1f, 0f);
17 | public static Vector2 Right => new(1f, 0f);
18 | public static Vector2 PositiveInfinity => new(float.PositiveInfinity, float.PositiveInfinity);
19 | public static Vector2 NegativeInfinity => new(float.NegativeInfinity, float.NegativeInfinity);
20 |
21 | public readonly float X;
22 | public readonly float Y;
23 |
24 | public Vector2(float x, float y)
25 | {
26 | X = x;
27 | Y = y;
28 | }
29 |
30 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
31 | public static Vector2 operator -(Vector2 a) => new(-a.X, -a.Y);
32 |
33 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
34 | public static Vector2 operator +(Vector2 a, Vector2 b) => new(a.X + b.X, a.Y + b.Y);
35 |
36 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
37 | public static Vector2 operator -(Vector2 a, Vector2 b) => new(a.X - b.X, a.Y - b.Y);
38 |
39 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
40 | public static Vector2 operator *(Vector2 a, Vector2 b) => new(a.X * b.X, a.Y * b.Y);
41 |
42 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
43 | public static Vector2 operator /(Vector2 a, Vector2 b) => new(a.X / b.X, a.Y / b.Y);
44 |
45 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
46 | public static Vector2 operator *(Vector2 a, float d) => new(a.X * d, a.Y * d);
47 |
48 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
49 | public static Vector2 operator *(float d, Vector2 a) => new(a.X * d, a.Y * d);
50 |
51 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
52 | public static Vector2 operator /(Vector2 a, float d) => new(a.X / d, a.Y / d);
53 |
54 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
55 | public bool Equals(Vector2 other)
56 | {
57 | return MathF.Abs(X - other.X) <= Epsilon && MathF.Abs(Y - other.Y) <= Epsilon;
58 | }
59 |
60 | public override bool Equals(object obj)
61 | {
62 | return obj is Vector2 other && Equals(other);
63 | }
64 |
65 | public override int GetHashCode()
66 | {
67 | return HashCode.Combine(X, Y);
68 | }
69 |
70 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
71 | public static bool operator ==(Vector2 left, Vector2 right)
72 | {
73 | return left.Equals(right);
74 | }
75 |
76 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
77 | public static bool operator !=(Vector2 left, Vector2 right)
78 | {
79 | return !left.Equals(right);
80 | }
81 |
82 | public override string ToString()
83 | {
84 | return $"({X}, {Y})";
85 | }
86 |
87 | public string ToString(string format, IFormatProvider formatProvider)
88 | {
89 | return $"({X.ToString(format, formatProvider)}, {Y.ToString(format, formatProvider)})";
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/Poltergeist.Core/Math/Vector2Int.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | namespace Poltergeist.Core.Math
6 | {
7 | [StructLayout(LayoutKind.Sequential)]
8 | public readonly struct Vector2Int : IEquatable, IFormattable
9 | {
10 | public static Vector2Int Zero => new(0, 0);
11 | public static Vector2Int One => new(1, 1);
12 | public static Vector2Int Up => new(0, 1);
13 | public static Vector2Int Down => new(0, -1);
14 | public static Vector2Int Left => new(-1, 0);
15 | public static Vector2Int Right => new(1, 0);
16 |
17 | public readonly int X;
18 | public readonly int Y;
19 |
20 | public Vector2Int(int x, int y)
21 | {
22 | X = x;
23 | Y = y;
24 | }
25 |
26 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
27 | public static Vector2Int operator -(Vector2Int a) => new(-a.X, -a.Y);
28 |
29 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
30 | public static Vector2Int operator +(Vector2Int a, Vector2Int b) => new(a.X + b.X, a.Y + b.Y);
31 |
32 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
33 | public static Vector2Int operator -(Vector2Int a, Vector2Int b) => new(a.X - b.X, a.Y - b.Y);
34 |
35 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
36 | public static Vector2Int operator *(Vector2Int a, Vector2Int b) => new(a.X * b.X, a.Y * b.Y);
37 |
38 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
39 | public static Vector2Int operator /(Vector2Int a, Vector2Int b) => new(a.X / b.X, a.Y / b.Y);
40 |
41 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
42 | public static Vector2Int operator *(Vector2Int a, int d) => new(a.X * d, a.Y * d);
43 |
44 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
45 | public static Vector2Int operator *(int d, Vector2Int a) => new(a.X * d, a.Y * d);
46 |
47 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
48 | public static Vector2Int operator /(Vector2Int a, int d) => new(a.X / d, a.Y / d);
49 |
50 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
51 | public bool Equals(Vector2Int other)
52 | {
53 | return X == other.X && Y == other.Y;
54 | }
55 |
56 | public override bool Equals(object obj)
57 | {
58 | return obj is Vector2Int other && Equals(other);
59 | }
60 |
61 | public override int GetHashCode()
62 | {
63 | return HashCode.Combine(X, Y);
64 | }
65 |
66 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
67 | public static bool operator ==(Vector2Int left, Vector2Int right)
68 | {
69 | return left.Equals(right);
70 | }
71 |
72 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
73 | public static bool operator !=(Vector2Int left, Vector2Int right)
74 | {
75 | return !left.Equals(right);
76 | }
77 |
78 | public override string ToString()
79 | {
80 | return $"({X}, {Y})";
81 | }
82 |
83 | public string ToString(string format, IFormatProvider formatProvider)
84 | {
85 | return $"({X.ToString(format, formatProvider)}, {Y.ToString(format, formatProvider)})";
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/Poltergeist.Core/Math/Vector3.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | namespace Poltergeist.Core.Math
6 | {
7 | [StructLayout(LayoutKind.Sequential)]
8 | public readonly struct Vector3 : IEquatable, IFormattable
9 | {
10 | private const float Epsilon = 0.001f;
11 |
12 | public static Vector3 Zero => new(0F, 0F, 0F);
13 | public static Vector3 One => new(1F, 1F, 1F);
14 | public static Vector3 Forward => new(0F, 0F, 1F);
15 | public static Vector3 Back => new(0F, 0F, -1F);
16 | public static Vector3 Up => new(0F, 1F, 0F);
17 | public static Vector3 Down => new(0F, -1F, 0F);
18 | public static Vector3 Left => new(-1F, 0F, 0F);
19 | public static Vector3 Right => new(1F, 0F, 0F);
20 | public static Vector3 PositiveInfinity => new(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
21 | public static Vector3 NegativeInfinity => new(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity);
22 |
23 | public readonly float X;
24 | public readonly float Y;
25 | public readonly float Z;
26 |
27 | public Vector3(float x, float y, float z)
28 | {
29 | X = x;
30 | Y = y;
31 | Z = z;
32 | }
33 |
34 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
35 | public static Vector3 operator -(Vector3 a) => new(-a.X, -a.Y, -a.Z);
36 |
37 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
38 | public static Vector3 operator +(Vector3 a, Vector3 b) => new(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
39 |
40 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
41 | public static Vector3 operator -(Vector3 a, Vector3 b) => new(a.X - b.X, a.Y + b.Y, a.Z + b.Z);
42 |
43 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
44 | public static Vector3 operator *(Vector3 a, Vector3 b) => new(a.X * b.X, a.Y * b.Y, a.Z * b.Z);
45 |
46 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
47 | public static Vector3 operator /(Vector3 a, Vector3 b) => new(a.X / b.X, a.Y / b.Y, a.Z / b.Z);
48 |
49 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
50 | public static Vector3 operator *(Vector3 a, float d) => new(a.X * d, a.Y * d, a.Z * d);
51 |
52 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
53 | public static Vector3 operator *(float d, Vector3 a) => new(a.X * d, a.Y * d, a.Z * d);
54 |
55 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
56 | public static Vector3 operator /(Vector3 a, float d) => new(a.X / d, a.Y / d, a.Z / d);
57 |
58 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
59 | public bool Equals(Vector3 other)
60 | {
61 | return MathF.Abs(X - other.X) <= Epsilon && MathF.Abs(Y - other.Y) <= Epsilon && MathF.Abs(Z - other.Z) <= Epsilon;
62 | }
63 |
64 | public override bool Equals(object obj)
65 | {
66 | return obj is Vector3 other && Equals(other);
67 | }
68 |
69 | public override int GetHashCode()
70 | {
71 | return HashCode.Combine(X, Y, Z);
72 | }
73 |
74 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
75 | public static bool operator ==(Vector3 left, Vector3 right)
76 | {
77 | return left.Equals(right);
78 | }
79 |
80 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
81 | public static bool operator !=(Vector3 left, Vector3 right)
82 | {
83 | return !left.Equals(right);
84 | }
85 |
86 | public override string ToString()
87 | {
88 | return $"({X}, {Y}, {Z})";
89 | }
90 |
91 | public string ToString(string format, IFormatProvider formatProvider)
92 | {
93 | return $"({X.ToString(format, formatProvider)}, {Y.ToString(format, formatProvider)}, {Z.ToString(format, formatProvider)})";
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/Poltergeist.Core/Math/Vector3Int.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | namespace Poltergeist.Core.Math
6 | {
7 | [StructLayout(LayoutKind.Sequential)]
8 | public readonly struct Vector3Int : IEquatable, IFormattable
9 | {
10 | public static Vector3Int Zero => new(0, 0, 0);
11 | public static Vector3Int One => new(1, 1, 1);
12 | public static Vector3Int Up => new(0, 1, 0);
13 | public static Vector3Int Down => new(0, -1, 0);
14 | public static Vector3Int Left => new(-1, 0, 0);
15 | public static Vector3Int Right => new(1, 0, 0);
16 |
17 | public readonly int X;
18 | public readonly int Y;
19 | public readonly int Z;
20 |
21 | public Vector3Int(int x, int y, int z)
22 | {
23 | X = x;
24 | Y = y;
25 | Z = z;
26 | }
27 |
28 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
29 | public static Vector3Int operator -(Vector3Int a) => new(-a.X, -a.Y, -a.Z);
30 |
31 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
32 | public static Vector3Int operator +(Vector3Int a, Vector3Int b) => new(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
33 |
34 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
35 | public static Vector3Int operator -(Vector3Int a, Vector3Int b) => new(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
36 |
37 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
38 | public static Vector3Int operator *(Vector3Int a, Vector3Int b) => new(a.X * b.X, a.Y * b.Y, a.Z * b.Z);
39 |
40 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
41 | public static Vector3Int operator /(Vector3Int a, Vector3Int b) => new(a.X / b.X, a.Y / b.Y, a.Z / b.Z);
42 |
43 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
44 | public static Vector3Int operator *(Vector3Int a, int d) => new(a.X * d, a.Y * d, a.Z * d);
45 |
46 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
47 | public static Vector3Int operator *(int d, Vector3Int a) => new(a.X * d, a.Y * d, a.Z * d);
48 |
49 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
50 | public static Vector3Int operator /(Vector3Int a, int d) => new(a.X / d, a.Y / d, a.Z / d);
51 |
52 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
53 | public bool Equals(Vector3Int other)
54 | {
55 | return X == other.X && Y == other.Y && Z == other.Z;
56 | }
57 |
58 | public override bool Equals(object obj)
59 | {
60 | return obj is Vector3Int other && Equals(other);
61 | }
62 |
63 | public override int GetHashCode()
64 | {
65 | return HashCode.Combine(X, Y, Z);
66 | }
67 |
68 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
69 | public static bool operator ==(Vector3Int left, Vector3Int right)
70 | {
71 | return left.Equals(right);
72 | }
73 |
74 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
75 | public static bool operator !=(Vector3Int left, Vector3Int right)
76 | {
77 | return !left.Equals(right);
78 | }
79 |
80 | public override string ToString()
81 | {
82 | return $"({X}, {Y}, {Z})";
83 | }
84 |
85 | public string ToString(string format, IFormatProvider formatProvider)
86 | {
87 | return $"({X.ToString(format, formatProvider)}, {Y.ToString(format, formatProvider)}, {Z.ToString(format, formatProvider)})";
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Poltergeist.Core/Math/Vector4.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | namespace Poltergeist.Core.Math
6 | {
7 | [StructLayout(LayoutKind.Sequential)]
8 | public readonly struct Vector4 : IEquatable, IFormattable
9 | {
10 | private const float Epsilon = 0.001f;
11 |
12 | public static Vector4 Zero => new(0F, 0F, 0F, 0F);
13 | public static Vector4 One => new(1F, 1F, 1F, 1F);
14 | public static Vector4 PositiveInfinity => new(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
15 | public static Vector4 NegativeInfinity => new(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity);
16 |
17 | public readonly float X;
18 | public readonly float Y;
19 | public readonly float Z;
20 | public readonly float W;
21 |
22 | public Vector4(float x, float y, float z, float w)
23 | {
24 | X = x;
25 | Y = y;
26 | Z = z;
27 | W = w;
28 | }
29 |
30 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
31 | public static Vector4 operator -(Vector4 a) => new(-a.X, -a.Y, -a.Z, -a.W);
32 |
33 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
34 | public static Vector4 operator +(Vector4 a, Vector4 b) => new(a.X + b.X, a.Y + b.Y, a.Z + b.Z, a.W + b.W);
35 |
36 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
37 | public static Vector4 operator -(Vector4 a, Vector4 b) => new(a.X - b.X, a.Y + b.Y, a.Z + b.Z, a.W + b.W);
38 |
39 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
40 | public static Vector4 operator *(Vector4 a, Vector4 b) => new(a.X * b.X, a.Y * b.Y, a.Z * b.Z, a.W * b.W);
41 |
42 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
43 | public static Vector4 operator /(Vector4 a, Vector4 b) => new(a.X / b.X, a.Y / b.Y, a.Z / b.Z, a.W / b.W);
44 |
45 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
46 | public static Vector4 operator *(Vector4 a, float d) => new(a.X * d, a.Y * d, a.Z * d, a.W * d);
47 |
48 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
49 | public static Vector4 operator *(float d, Vector4 a) => new(a.X * d, a.Y * d, a.Z * d, a.W * d);
50 |
51 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
52 | public static Vector4 operator /(Vector4 a, float d) => new(a.X / d, a.Y / d, a.Z / d, a.W / d);
53 |
54 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
55 | public bool Equals(Vector4 other)
56 | {
57 | return MathF.Abs(X - other.X) <= Epsilon && MathF.Abs(Y - other.Y) <= Epsilon && MathF.Abs(Z - other.Z) <= Epsilon && MathF.Abs(W - other.W) <= Epsilon;
58 | }
59 |
60 | public override bool Equals(object obj)
61 | {
62 | return obj is Vector4 other && Equals(other);
63 | }
64 |
65 | public override int GetHashCode()
66 | {
67 | return HashCode.Combine(X, Y, Z, W);
68 | }
69 |
70 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
71 | public static bool operator ==(Vector4 left, Vector4 right)
72 | {
73 | return left.Equals(right);
74 | }
75 |
76 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
77 | public static bool operator !=(Vector4 left, Vector4 right)
78 | {
79 | return !left.Equals(right);
80 | }
81 |
82 | public override string ToString()
83 | {
84 | return $"({X}, {Y}, {Z}, {W})";
85 | }
86 |
87 | public string ToString(string format, IFormatProvider formatProvider)
88 | {
89 | return $"({X.ToString(format, formatProvider)}, {Y.ToString(format, formatProvider)}, {Z.ToString(format, formatProvider)}, {W.ToString(format, formatProvider)})";
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/Poltergeist.Core/Math/Vector4Int.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | namespace Poltergeist.Core.Math
6 | {
7 | [StructLayout(LayoutKind.Sequential)]
8 | public readonly struct Vector4Int : IEquatable, IFormattable
9 | {
10 | public static Vector4Int Zero => new(0, 0, 0, 0);
11 | public static Vector4Int One => new(1, 1, 1, 1);
12 |
13 | public readonly int X;
14 | public readonly int Y;
15 | public readonly int Z;
16 | public readonly int W;
17 |
18 | public Vector4Int(int x, int y, int z, int w)
19 | {
20 | X = x;
21 | Y = y;
22 | Z = z;
23 | W = w;
24 | }
25 |
26 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
27 | public static Vector4Int operator -(Vector4Int a) => new(-a.X, -a.Y, -a.Z, -a.W);
28 |
29 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
30 | public static Vector4Int operator +(Vector4Int a, Vector4Int b) => new(a.X + b.X, a.Y + b.Y, a.Z + b.Z, a.W + b.W);
31 |
32 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
33 | public static Vector4Int operator -(Vector4Int a, Vector4Int b) => new(a.X - b.X, a.Y - b.Y, a.Z - b.Z, a.W - b.W);
34 |
35 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
36 | public static Vector4Int operator *(Vector4Int a, Vector4Int b) => new(a.X * b.X, a.Y * b.Y, a.Z * b.Z, a.W * b.W);
37 |
38 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
39 | public static Vector4Int operator /(Vector4Int a, Vector4Int b) => new(a.X / b.X, a.Y / b.Y, a.Z / b.Z, a.W / b.W);
40 |
41 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
42 | public static Vector4Int operator *(Vector4Int a, int d) => new(a.X * d, a.Y * d, a.Z * d, a.W * d);
43 |
44 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
45 | public static Vector4Int operator *(int d, Vector4Int a) => new(a.X * d, a.Y * d, a.Z * d, a.W * d);
46 |
47 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
48 | public static Vector4Int operator /(Vector4Int a, int d) => new(a.X / d, a.Y / d, a.Z / d, a.W / d);
49 |
50 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
51 | public bool Equals(Vector4Int other)
52 | {
53 | return X == other.X && Y == other.Y && Z == other.Z && W == other.W;
54 | }
55 |
56 | public override bool Equals(object obj)
57 | {
58 | return obj is Vector4Int other && Equals(other);
59 | }
60 |
61 | public override int GetHashCode()
62 | {
63 | return HashCode.Combine(X, Y, Z, W);
64 | }
65 |
66 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
67 | public static bool operator ==(Vector4Int left, Vector4Int right)
68 | {
69 | return left.Equals(right);
70 | }
71 |
72 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
73 | public static bool operator !=(Vector4Int left, Vector4Int right)
74 | {
75 | return !left.Equals(right);
76 | }
77 |
78 | public override string ToString()
79 | {
80 | return $"({X}, {Y}, {Z}, {W})";
81 | }
82 |
83 | public string ToString(string format, IFormatProvider formatProvider)
84 | {
85 | return $"({X.ToString(format, formatProvider)}, {Y.ToString(format, formatProvider)}, {Z.ToString(format, formatProvider)}, {W.ToString(format, formatProvider)})";
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/Poltergeist.Core/Memory/CoTaskMemAllocator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Runtime.InteropServices;
4 |
5 | namespace Poltergeist.Core.Memory
6 | {
7 | public sealed class CoTaskMemAllocator : INativeAllocator
8 | {
9 | private readonly HashSet _allocatedPointers = new();
10 | private readonly object _lock = new();
11 |
12 | public IntPtr Allocate(int size)
13 | {
14 | if (size < 0)
15 | throw new ArgumentOutOfRangeException(nameof(size), size, "Allocation size must be positive");
16 | IntPtr ptr = Marshal.AllocCoTaskMem(size);
17 | if (ptr == IntPtr.Zero)
18 | throw new NullReferenceException();
19 | lock (_lock)
20 | _allocatedPointers.Add(ptr);
21 | return ptr;
22 | }
23 |
24 | public void Free(IntPtr data)
25 | {
26 | lock (_lock)
27 | if (!_allocatedPointers.Remove(data))
28 | throw new InvalidOperationException("Tried to doublefree or free invalid data");
29 | Marshal.FreeCoTaskMem(data);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Poltergeist.Core/Memory/INativeAllocator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Poltergeist.Core.Memory
4 | {
5 | public interface INativeAllocator
6 | {
7 | IntPtr Allocate(int size);
8 | void Free(IntPtr data);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Poltergeist.Core/Memory/NativeArray.cs:
--------------------------------------------------------------------------------
1 | using Poltergeist.Core.Math;
2 | using System;
3 | using System.Buffers;
4 | using System.Collections;
5 | using System.Collections.Generic;
6 | using System.Diagnostics;
7 | using System.Diagnostics.Contracts;
8 | using System.Numerics;
9 | using System.Runtime.CompilerServices;
10 |
11 | // ReSharper disable UnusedType.Global
12 | // ReSharper disable MemberCanBePrivate.Global
13 |
14 | namespace Poltergeist.Core.Memory
15 | {
16 | public sealed unsafe class NativeArray : MemoryManager, IReadOnlyList, IDisposable where T : unmanaged
17 | {
18 | // ReSharper disable StaticMemberInGenericType
19 | public static readonly int MaxSize = int.MaxValue / sizeof(T);
20 | public static readonly int DefaultMemoryAlignment = System.Math.Max(System.Math.Max(16, sizeof(T)), MathUtils.Align(Vector.Count, 16));
21 | public static readonly INativeAllocator DefaultAllocator = new CoTaskMemAllocator();
22 | // ReSharper restore StaticMemberInGenericType
23 |
24 | public readonly T* Data;
25 | public readonly int Count;
26 | public readonly int ByteSize;
27 |
28 | internal readonly INativeAllocator Allocator;
29 | internal readonly int AlignedSize;
30 |
31 | private readonly object _freeLock = new();
32 | private readonly bool _zeroOnFree;
33 |
34 | #if DEBUG
35 | private readonly string _allocationStacktrace;
36 | #endif
37 |
38 | private bool _valid;
39 |
40 | int IReadOnlyCollection.Count => Count;
41 |
42 | public NativeArray(ReadOnlySpan data, bool zeroOnFree = false, INativeAllocator allocator = null)
43 | : this(data, DefaultMemoryAlignment, zeroOnFree, allocator)
44 | {
45 | }
46 |
47 | public NativeArray(ReadOnlySpan data, int alignment, bool zeroOnFree = false, INativeAllocator allocator = null)
48 | : this(data.Length, alignment, false, zeroOnFree, allocator)
49 | {
50 | data.CopyTo(AsSpan());
51 | }
52 |
53 | public NativeArray(int size, bool zeroMemory = true, bool zeroOnFree = false, INativeAllocator allocator = null)
54 | : this(size, DefaultMemoryAlignment, zeroMemory, zeroOnFree, allocator)
55 | {
56 | }
57 |
58 | public NativeArray(int size, int alignment, bool zeroMemory = true, bool zeroOnFree = false, INativeAllocator allocator = null)
59 | {
60 | if (size < 0)
61 | throw new ArgumentOutOfRangeException(nameof(size), size, "Allocation size must be positive");
62 | if (alignment < sizeof(T))
63 | throw new ArgumentOutOfRangeException(nameof(alignment), alignment, "Alignment must be at least the element size");
64 | if (size > MaxSize)
65 | throw new ArgumentOutOfRangeException(nameof(size), size, "Allocation size must be below 2GB");
66 | Count = size;
67 | ByteSize = size * sizeof(T);
68 | Allocator = allocator ?? DefaultAllocator;
69 | AlignedSize = ByteSize == 0 ? 0 : (int)System.Math.Min((uint)MathUtils.Align(ByteSize, alignment), int.MaxValue);
70 | Data = (T*)Allocator.Allocate(AlignedSize).ToPointer();
71 | _valid = Data != null;
72 |
73 | if (AlignedSize == 0)
74 | return;
75 | GC.AddMemoryPressure(AlignedSize);
76 | if (zeroMemory)
77 | Clear();
78 | _zeroOnFree = zeroOnFree;
79 |
80 | #if DEBUG
81 | _allocationStacktrace = Environment.StackTrace;
82 | #endif
83 | }
84 |
85 | public T this[int index]
86 | {
87 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
88 | get
89 | {
90 | if (index >= Count || index < 0)
91 | ThrowHelper.IndexOutOfRange();
92 | return Data[index];
93 | }
94 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
95 | set
96 | {
97 | if (index >= Count || index < 0)
98 | ThrowHelper.IndexOutOfRange();
99 | Data[index] = value;
100 | }
101 | }
102 |
103 | public T this[uint index]
104 | {
105 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
106 | get
107 | {
108 | if (index >= Count)
109 | ThrowHelper.IndexOutOfRange();
110 | return Data[index];
111 | }
112 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
113 | set
114 | {
115 | if (index >= Count)
116 | ThrowHelper.IndexOutOfRange();
117 | Data[index] = value;
118 | }
119 | }
120 |
121 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
122 | public void CopyTo(Span destination)
123 | {
124 | AsReadOnlySpan().CopyTo(destination);
125 | }
126 |
127 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
128 | public bool TryCopyTo(Span destination)
129 | {
130 | return AsReadOnlySpan().TryCopyTo(destination);
131 | }
132 |
133 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
134 | public void Fill(T value)
135 | {
136 | AsSpan().Fill(value);
137 | }
138 |
139 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
140 | public void Clear()
141 | {
142 | AsSpan().Clear();
143 | }
144 |
145 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
146 | public Span AsSpan()
147 | {
148 | return new(Data, Count);
149 | }
150 |
151 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
152 | public ReadOnlySpan AsReadOnlySpan()
153 | {
154 | return new(Data, Count);
155 | }
156 |
157 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
158 | public T[] ToArray()
159 | {
160 | if (Count == 0)
161 | return Array.Empty();
162 |
163 | T[] destination = new T[Count];
164 | CopyTo(destination);
165 | return destination;
166 | }
167 |
168 | #region MemoryOwner
169 | protected override void Dispose(bool disposing) => Dispose();
170 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
171 | public override Span GetSpan() => AsSpan();
172 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
173 | public override MemoryHandle Pin(int elementIndex = 0) => new(Data + elementIndex, pinnable: this);
174 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
175 | public override void Unpin() { }
176 |
177 | protected override bool TryGetArray(out ArraySegment segment)
178 | {
179 | segment = default;
180 | return false;
181 | }
182 | #endregion
183 |
184 | #region IEnumerable
185 | public NativeArrayEnumerator GetEnumerator() => new(this);
186 | // ReSharper disable HeapView.BoxingAllocation
187 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
188 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
189 | // ReSharper restore HeapView.BoxingAllocation
190 | #endregion
191 |
192 | #region IDisposeable
193 | private void Free()
194 | {
195 | lock (_freeLock)
196 | {
197 | if (!_valid)
198 | return;
199 | if (_zeroOnFree)
200 | Clear();
201 | Allocator.Free(new IntPtr(Data));
202 | if (AlignedSize > 0)
203 | GC.RemoveMemoryPressure(AlignedSize);
204 | _valid = false;
205 | }
206 | }
207 |
208 | public void Dispose()
209 | {
210 | Free();
211 | GC.SuppressFinalize(this);
212 | }
213 |
214 | #pragma warning disable CA2015
215 | ~NativeArray()
216 | {
217 | #if DEBUG
218 | Debug.WriteLine($"NativeArray leaked to the GC, this might lead to use after free with spans. Allocation stacktrace: {_allocationStacktrace}");
219 | #endif
220 | Free();
221 | }
222 | #pragma warning restore CA2015
223 | #endregion
224 |
225 | public struct NativeArrayEnumerator : IEnumerator
226 | {
227 | private readonly NativeArray _array;
228 | private int _currentIndex;
229 |
230 | public T Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; [MethodImpl(MethodImplOptions.AggressiveInlining)] private set; }
231 | // ReSharper disable once HeapView.BoxingAllocation
232 | object IEnumerator.Current => Current;
233 |
234 | internal NativeArrayEnumerator(NativeArray array)
235 | {
236 | _array = array;
237 | _currentIndex = -1;
238 | Current = default;
239 | }
240 |
241 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
242 | public bool MoveNext()
243 | {
244 | if (++_currentIndex >= _array.Count)
245 | return false;
246 | Current = _array.Data[_currentIndex];
247 | return true;
248 | }
249 |
250 | public void Reset() => _currentIndex = -1;
251 | public void Dispose() { }
252 | }
253 |
254 | [Pure]
255 | private static class ThrowHelper
256 | {
257 | public static void IndexOutOfRange() => throw new IndexOutOfRangeException();
258 | }
259 | }
260 | }
261 |
--------------------------------------------------------------------------------
/Poltergeist.Core/Memory/NativeArrayPool.cs:
--------------------------------------------------------------------------------
1 | // Licensed to the .NET Foundation under one or more agreements.
2 | // The .NET Foundation licenses this file to you under the MIT license.
3 | // Code is a modified version of TlsOverPerCoreLockedStacksArrayPool from .NET 5
4 |
5 | using System;
6 | using System.Numerics;
7 | using System.Runtime.CompilerServices;
8 | using System.Threading;
9 |
10 | namespace Poltergeist.Core.Memory
11 | {
12 | public static class NativeArrayPool where T : unmanaged
13 | {
14 | private const int NumBuckets = 17;
15 | private const int MaxPerCorePerArraySizeStacks = 64;
16 | private const int MaxBuffersPerArraySizePerCore = 8;
17 |
18 | private static readonly NativeArray Empty = new(0, false);
19 |
20 | // ReSharper disable once StaticMemberInGenericType
21 | private static readonly int[] BucketArraySizes;
22 | private static readonly PerCoreLockedStacks[] Buckets = new PerCoreLockedStacks[NumBuckets];
23 |
24 | [ThreadStatic]
25 | private static NativeArray[] _tTlsBuckets;
26 |
27 | static NativeArrayPool()
28 | {
29 | int[] sizes = new int[NumBuckets];
30 | for (int i = 0; i < sizes.Length; i++)
31 | sizes[i] = Utilities.GetMaxSizeForBucket(i);
32 | BucketArraySizes = sizes;
33 | }
34 |
35 | private static PerCoreLockedStacks CreatePerCoreLockedStacks(int bucketIndex)
36 | {
37 | PerCoreLockedStacks stacks = new();
38 | return Interlocked.CompareExchange(ref Buckets[bucketIndex], stacks, null) ?? stacks;
39 | }
40 |
41 | public static NativeArray Rent(int minimumLength)
42 | {
43 | switch (minimumLength)
44 | {
45 | case < 0:
46 | throw new ArgumentOutOfRangeException(nameof(minimumLength));
47 | case 0:
48 | return Empty;
49 | }
50 |
51 | int bucketIndex = Utilities.SelectBucketIndex(minimumLength);
52 |
53 | if (bucketIndex >= Buckets.Length)
54 | return new NativeArray(minimumLength, false);
55 |
56 | NativeArray[] tlsBuckets = _tTlsBuckets;
57 | if (tlsBuckets != null)
58 | {
59 | NativeArray buffer = tlsBuckets[bucketIndex];
60 | if (buffer != null)
61 | {
62 | tlsBuckets[bucketIndex] = null;
63 | return buffer;
64 | }
65 | }
66 |
67 | PerCoreLockedStacks bucket = Buckets[bucketIndex];
68 | if (bucket != null)
69 | {
70 | NativeArray buffer = bucket.TryPop();
71 | if (buffer != null)
72 | return buffer;
73 | }
74 |
75 | return new NativeArray(BucketArraySizes[bucketIndex], false);
76 | }
77 |
78 | public static void Return(NativeArray nativeArray, bool clearArray = false)
79 | {
80 | if (nativeArray == null)
81 | throw new ArgumentNullException(nameof(nativeArray));
82 |
83 | if (nativeArray == Empty)
84 | return;
85 |
86 | if (nativeArray.Count == 0)
87 | throw new ArgumentException("Memory not from pool", nameof(nativeArray));
88 |
89 | int bucketIndex = Utilities.SelectBucketIndex(nativeArray.Count);
90 |
91 | if (bucketIndex >= Buckets.Length)
92 | {
93 | nativeArray.Dispose();
94 | return;
95 | }
96 |
97 | if (clearArray)
98 | nativeArray.Clear();
99 |
100 | if (nativeArray.Count != BucketArraySizes[bucketIndex])
101 | throw new ArgumentException("Memory not from pool", nameof(nativeArray));
102 |
103 | NativeArray[] tlsBuckets = _tTlsBuckets;
104 | if (tlsBuckets == null)
105 | {
106 | _tTlsBuckets = tlsBuckets = new NativeArray[NumBuckets];
107 | tlsBuckets[bucketIndex] = nativeArray;
108 | return;
109 | }
110 |
111 | NativeArray prev = tlsBuckets[bucketIndex];
112 | tlsBuckets[bucketIndex] = nativeArray;
113 |
114 | if (prev == null)
115 | return;
116 |
117 | PerCoreLockedStacks stackBucket = Buckets[bucketIndex] ?? CreatePerCoreLockedStacks(bucketIndex);
118 | stackBucket.TryPush(prev);
119 | }
120 |
121 | private sealed class PerCoreLockedStacks
122 | {
123 | private readonly LockedStack[] _perCoreStacks;
124 |
125 | public PerCoreLockedStacks()
126 | {
127 | LockedStack[] stacks = new LockedStack[System.Math.Min(Environment.ProcessorCount, MaxPerCorePerArraySizeStacks)];
128 | for (int i = 0; i < stacks.Length; i++)
129 | stacks[i] = new LockedStack();
130 | _perCoreStacks = stacks;
131 | }
132 |
133 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
134 | public void TryPush(NativeArray array)
135 | {
136 | LockedStack[] stacks = _perCoreStacks;
137 | int index = Thread.GetCurrentProcessorId() % stacks.Length;
138 | for (int i = 0; i < stacks.Length; i++)
139 | {
140 | if (stacks[index].TryPush(array))
141 | return;
142 | if (++index == stacks.Length)
143 | index = 0;
144 | }
145 | }
146 |
147 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
148 | public NativeArray TryPop()
149 | {
150 | LockedStack[] stacks = _perCoreStacks;
151 | int index = Thread.GetCurrentProcessorId() % stacks.Length;
152 | for (int i = 0; i < stacks.Length; i++)
153 | {
154 | NativeArray arr = stacks[index].TryPop();
155 | if (arr != null)
156 | return arr;
157 | if (++index == stacks.Length)
158 | index = 0;
159 | }
160 | return null;
161 | }
162 | }
163 |
164 | private sealed class LockedStack
165 | {
166 | private readonly NativeArray[] _arrays = new NativeArray[MaxBuffersPerArraySizePerCore];
167 | private int _count;
168 |
169 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
170 | public bool TryPush(NativeArray array)
171 | {
172 | bool enqueued = false;
173 | Monitor.Enter(this);
174 | if (_count < MaxBuffersPerArraySizePerCore)
175 | {
176 | _arrays[_count++] = array;
177 | enqueued = true;
178 | }
179 | Monitor.Exit(this);
180 | return enqueued;
181 | }
182 |
183 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
184 | public NativeArray TryPop()
185 | {
186 | NativeArray nativeArray = null;
187 | Monitor.Enter(this);
188 | if (_count > 0)
189 | {
190 | nativeArray = _arrays[--_count];
191 | _arrays[_count] = null;
192 | }
193 | Monitor.Exit(this);
194 | return nativeArray;
195 | }
196 | }
197 |
198 | private static class Utilities
199 | {
200 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
201 | public static int SelectBucketIndex(int bufferSize) => BitOperations.Log2((uint)bufferSize - 1 | 15) - 3;
202 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
203 | public static int GetMaxSizeForBucket(int binIndex) => 16 << binIndex;
204 | }
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/Poltergeist.Core/Memory/NativeList.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Diagnostics.Contracts;
6 | using System.Runtime.CompilerServices;
7 | using System.Runtime.InteropServices;
8 |
9 | // ReSharper disable UnusedType.Global
10 | // ReSharper disable MemberCanBePrivate.Global
11 |
12 | namespace Poltergeist.Core.Memory
13 | {
14 | public sealed unsafe class NativeList : IList, IReadOnlyList, IList, IDisposable where T : unmanaged, IEquatable
15 | {
16 | private const int DefaultCapacity = 8;
17 |
18 | // ReSharper disable StaticMemberInGenericType
19 | public static readonly int MaxSize = int.MaxValue / sizeof(T);
20 | public static readonly INativeAllocator DefaultAllocator = new CoTaskMemAllocator();
21 | // ReSharper restore StaticMemberInGenericType
22 |
23 | internal readonly INativeAllocator Allocator;
24 |
25 | private readonly object _freeLock = new();
26 | private readonly bool _zeroOnFree;
27 |
28 | #if DEBUG
29 | private readonly string _allocationStacktrace;
30 | #endif
31 |
32 | private bool _valid;
33 | private int _version;
34 |
35 | public T* Data { get; private set; }
36 | public int Count { get; private set; }
37 | public int Capacity { get; private set; }
38 |
39 | public bool IsSynchronized => false;
40 | public object SyncRoot => this;
41 | public bool IsReadOnly => false;
42 | public bool IsFixedSize => false;
43 |
44 | public NativeList(ReadOnlySpan data, bool zeroOnFree = false, INativeAllocator allocator = null)
45 | : this(System.Math.Max(data.Length, DefaultCapacity), zeroOnFree, allocator)
46 | {
47 | data.CopyTo(new Span(Data, Capacity));
48 | Count = data.Length;
49 | }
50 |
51 | public NativeList(int capacity = DefaultCapacity, bool zeroOnFree = false, INativeAllocator allocator = null)
52 | {
53 | if (capacity < 1)
54 | throw new ArgumentOutOfRangeException(nameof(capacity), capacity, "Capacity must be positive");
55 | if (capacity > MaxSize)
56 | throw new ArgumentOutOfRangeException(nameof(capacity), capacity, "Capacity must be below 2GB");
57 | Capacity = capacity;
58 | Allocator = allocator ?? DefaultAllocator;
59 | Data = (T*)Allocator.Allocate(capacity * sizeof(T)).ToPointer();
60 | _valid = Data != null;
61 |
62 | GC.AddMemoryPressure(capacity * sizeof(T));
63 | _zeroOnFree = zeroOnFree;
64 |
65 | #if DEBUG
66 | _allocationStacktrace = Environment.StackTrace;
67 | #endif
68 | }
69 |
70 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
71 | public void EnsureCapacity(int capacity)
72 | {
73 | if (Capacity >= capacity)
74 | return;
75 | Resize(capacity);
76 | }
77 |
78 | [MethodImpl(MethodImplOptions.NoInlining)]
79 | private void Resize(int capacity)
80 | {
81 | capacity = System.Math.Max(Capacity * 2, capacity);
82 | if (capacity > MaxSize || capacity < 1)
83 | throw new InvalidOperationException();
84 | int size = capacity * sizeof(T);
85 | T* newData = (T*)Allocator.Allocate(size).ToPointer();
86 | GC.AddMemoryPressure(size);
87 | CopyTo(new Span(newData, Count));
88 | if (_zeroOnFree)
89 | AsSpan().Clear();
90 | Allocator.Free(new IntPtr(Data));
91 | GC.RemoveMemoryPressure(Capacity * sizeof(T));
92 | Data = newData;
93 | Capacity = capacity;
94 | }
95 |
96 | object IList.this[int index]
97 | {
98 | // ReSharper disable once HeapView.BoxingAllocation
99 | get => this[index];
100 | set
101 | {
102 | if (value is not T v)
103 | throw new ArgumentException($"Argument is not of type {typeof(T).Name}", nameof(value));
104 | this[index] = v;
105 | }
106 | }
107 |
108 | public T this[int index]
109 | {
110 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
111 | get
112 | {
113 | if (index >= Count || index < 0)
114 | ThrowHelper.IndexOutOfRange();
115 | return Data[index];
116 | }
117 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
118 | set
119 | {
120 | if (index >= Count || index < 0)
121 | ThrowHelper.IndexOutOfRange();
122 | Data[index] = value;
123 | }
124 | }
125 |
126 | public T this[uint index]
127 | {
128 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
129 | get
130 | {
131 | if (index >= Count)
132 | ThrowHelper.IndexOutOfRange();
133 | return Data[index];
134 | }
135 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
136 | set
137 | {
138 | if (index >= Count)
139 | ThrowHelper.IndexOutOfRange();
140 | Data[index] = value;
141 | }
142 | }
143 |
144 | public int Add(object value)
145 | {
146 | if (value is not T v)
147 | throw new ArgumentException($"Argument is not of type {typeof(T).Name}", nameof(value));
148 | Add(v);
149 | return Count - 1;
150 | }
151 |
152 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
153 | public void Add(T item)
154 | {
155 | EnsureCapacity(Count + 1);
156 | Data[Count++] = item;
157 | _version++;
158 | }
159 |
160 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
161 | public void AddRange(ReadOnlySpan items)
162 | {
163 | EnsureCapacity(Count + items.Length);
164 | items.CopyTo(new Span(Data + Count, Capacity - Count));
165 | Count += items.Length;
166 | _version++;
167 | }
168 |
169 | public void Insert(int index, object value)
170 | {
171 | if (value is not T v)
172 | throw new ArgumentException($"Argument is not of type {typeof(T).Name}", nameof(value));
173 | Insert(index, v);
174 | }
175 |
176 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
177 | public void Insert(int index, T item)
178 | {
179 | if (index > Count || index < 0)
180 | ThrowHelper.IndexOutOfRange();
181 | EnsureCapacity(Count + 1);
182 | if (Count != index)
183 | new ReadOnlySpan(Data + index, Count - index).CopyTo(new Span(Data + index + 1, Count - index));
184 | Data[index] = item;
185 | Count++;
186 | _version++;
187 | }
188 |
189 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
190 | public void InsertRange(int index, ReadOnlySpan items)
191 | {
192 | if (index > Count || index < 0)
193 | ThrowHelper.IndexOutOfRange();
194 | EnsureCapacity(Count + items.Length);
195 | if (Count != index)
196 | new ReadOnlySpan(Data + index, Count - index).CopyTo(new Span(Data + index + items.Length, Count - index));
197 | items.CopyTo(new Span(Data + index, Capacity - index));
198 | Count += items.Length;
199 | _version++;
200 | }
201 |
202 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
203 | public void RemoveAt(int index)
204 | {
205 | if (index >= Count || index < 0)
206 | ThrowHelper.IndexOutOfRange();
207 | _version++;
208 | if (index == Count - 1)
209 | {
210 | Data[--Count] = default;
211 | return;
212 | }
213 |
214 | new ReadOnlySpan(Data + index + 1, Count - index - 1).CopyTo(new Span(Data + index, Count - index - 1));
215 | Data[--Count] = default;
216 | }
217 |
218 | public void Remove(object value)
219 | {
220 | if (value is not T v)
221 | throw new ArgumentException($"Argument is not of type {typeof(T).Name}", nameof(value));
222 | Remove(v);
223 | }
224 |
225 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
226 | public bool Remove(T item)
227 | {
228 | int i = IndexOf(item);
229 | if (i != -1)
230 | {
231 | RemoveAt(i);
232 | return true;
233 | }
234 |
235 | return false;
236 | }
237 |
238 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
239 | public void RemoveRange(int index, int count)
240 | {
241 | if (index >= Count || index < 0)
242 | ThrowHelper.IndexOutOfRange();
243 | // ReSharper disable HeapView.BoxingAllocation
244 | if (count < 0)
245 | ThrowHelper.ArgumentOutOfRange("Tried to remove a negative count of elements", nameof(count), count);
246 | if (Count - index < count)
247 | ThrowHelper.ArgumentOutOfRange("Tried to remove more elements than the collection contains after the index", nameof(count), count);
248 | // ReSharper restore HeapView.BoxingAllocation
249 |
250 | if (count == 0)
251 | return;
252 | new ReadOnlySpan(Data + index + count, Count - index + 1 - count).CopyTo(new Span(Data + index, Count - index + 1 - count));
253 | if (_zeroOnFree)
254 | new Span(Data + Count - count, count).Clear();
255 | Count -= count;
256 | _version++;
257 | }
258 |
259 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
260 | public void Clear()
261 | {
262 | if (_zeroOnFree)
263 | AsSpan().Clear();
264 | Count = 0;
265 | }
266 |
267 | public int IndexOf(object value)
268 | {
269 | if (value is not T v)
270 | throw new ArgumentException($"Argument is not of type {typeof(T).Name}", nameof(value));
271 | return IndexOf(v);
272 | }
273 |
274 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
275 | public int IndexOf(T item)
276 | {
277 | return AsReadOnlySpan().IndexOf(item);
278 | }
279 |
280 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
281 | public int LastIndexOf(T item)
282 | {
283 | return AsReadOnlySpan().LastIndexOf(item);
284 | }
285 |
286 | public bool Contains(object value)
287 | {
288 | if (value is not T v)
289 | throw new ArgumentException($"Argument is not of type {typeof(T).Name}", nameof(value));
290 | return Contains(v);
291 | }
292 |
293 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
294 | public bool Contains(T item)
295 | {
296 | return AsReadOnlySpan().Contains(item);
297 | }
298 |
299 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
300 | public void CopyTo(Array array, int index)
301 | {
302 | if (Count == 0)
303 | return;
304 | if (index >= array.Length || index < 0)
305 | ThrowHelper.IndexOutOfRange();
306 | if (array is T[] a)
307 | {
308 | CopyTo(new Span(a, index, a.Length - index));
309 | return;
310 | }
311 |
312 | CopyToSlow(array, index);
313 | }
314 |
315 | [MethodImpl(MethodImplOptions.NoInlining)]
316 | private void CopyToSlow(Array array, int index)
317 | {
318 | if (array.Rank != 1 || array.GetLowerBound(0) != 0)
319 | throw new ArgumentException("Only copies to single dimensional, zero based arrays are permitted", nameof(array));
320 | GCHandle handle = GCHandle.Alloc(array, GCHandleType.Pinned);
321 | try
322 | {
323 | CopyTo(new Span((T*)handle.AddrOfPinnedObject() + index, array.Length - index));
324 | }
325 | finally
326 | {
327 | handle.Free();
328 | }
329 | }
330 |
331 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
332 | public void CopyTo(T[] array, int index)
333 | {
334 | if (Count == 0)
335 | return;
336 | if (index >= array.Length || index < 0)
337 | ThrowHelper.IndexOutOfRange();
338 | CopyTo(new Span(array, index, array.Length - index));
339 | }
340 |
341 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
342 | public void CopyTo(Span destination)
343 | {
344 | AsReadOnlySpan().CopyTo(destination);
345 | }
346 |
347 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
348 | public bool TryCopyTo(Span destination)
349 | {
350 | return AsReadOnlySpan().TryCopyTo(destination);
351 | }
352 |
353 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
354 | public Span AsSpan()
355 | {
356 | return new(Data, Count);
357 | }
358 |
359 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
360 | public ReadOnlySpan AsReadOnlySpan()
361 | {
362 | return new(Data, Count);
363 | }
364 |
365 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
366 | public T[] ToArray()
367 | {
368 | if (Count == 0)
369 | return Array.Empty();
370 |
371 | T[] destination = new T[Count];
372 | CopyTo(destination);
373 | return destination;
374 | }
375 |
376 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
377 | public List ToList()
378 | {
379 | return new(this);
380 | }
381 |
382 | // ReSharper disable HeapView.BoxingAllocation
383 | public NativeListEnumerator GetEnumerator() => new(this);
384 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
385 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
386 | // ReSharper restore HeapView.BoxingAllocation
387 |
388 | private void Free()
389 | {
390 | lock (_freeLock)
391 | {
392 | if (!_valid)
393 | return;
394 | if (_zeroOnFree)
395 | AsSpan().Clear();
396 | Allocator.Free(new IntPtr(Data));
397 | GC.RemoveMemoryPressure(Capacity * sizeof(T));
398 | _valid = false;
399 | }
400 | }
401 |
402 | public void Dispose()
403 | {
404 | Free();
405 | GC.SuppressFinalize(this);
406 | }
407 |
408 | ~NativeList()
409 | {
410 | #if DEBUG
411 | Debug.WriteLine($"NativeList leaked to the GC, this might lead to use after free with spans. Allocation stacktrace: {_allocationStacktrace}");
412 | #endif
413 | Free();
414 | }
415 |
416 | public struct NativeListEnumerator : IEnumerator
417 | {
418 | private readonly NativeList _list;
419 | private readonly int _version;
420 | private int _currentIndex;
421 |
422 | public T Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; [MethodImpl(MethodImplOptions.AggressiveInlining)] private set; }
423 | // ReSharper disable once HeapView.BoxingAllocation
424 | object IEnumerator.Current => Current;
425 |
426 | internal NativeListEnumerator(NativeList list)
427 | {
428 | _list = list;
429 | _currentIndex = -1;
430 | _version = list._version;
431 | Current = default;
432 | }
433 |
434 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
435 | public bool MoveNext()
436 | {
437 | if (_version != _list._version)
438 | ThrowHelper.InvalidOperation();
439 | if (++_currentIndex >= _list.Count)
440 | return false;
441 | Current = _list.Data[_currentIndex];
442 | return true;
443 | }
444 |
445 | public void Reset()
446 | {
447 | if (_version != _list._version)
448 | ThrowHelper.InvalidOperation();
449 | _currentIndex = -1;
450 | }
451 |
452 | public void Dispose() { }
453 | }
454 |
455 | [Pure]
456 | private static class ThrowHelper
457 | {
458 | public static void IndexOutOfRange() => throw new IndexOutOfRangeException();
459 | public static void InvalidOperation() => throw new InvalidOperationException();
460 | public static void ArgumentOutOfRange(string message, string name, object value) => throw new ArgumentOutOfRangeException(name, value, message);
461 | }
462 | }
463 | }
464 |
--------------------------------------------------------------------------------
/Poltergeist.Core/Memory/NativeStruct.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 | namespace Poltergeist.Core.Memory
5 | {
6 | public sealed unsafe class NativeStruct : IDisposable where T : struct
7 | {
8 | // ReSharper disable once StaticMemberInGenericType
9 | public static readonly INativeAllocator DefaultAllocator = new CoTaskMemAllocator();
10 |
11 | public readonly void* Data;
12 | public readonly int Size;
13 |
14 | internal readonly INativeAllocator Allocator;
15 |
16 | private readonly bool _zeroOnFree;
17 | private readonly object _disposeLock = new();
18 |
19 | private bool _valid;
20 |
21 | public NativeStruct(T structure, bool zeroOnFree = false, INativeAllocator allocator = null)
22 | {
23 | Size = Marshal.SizeOf();
24 |
25 | Allocator = allocator ?? DefaultAllocator;
26 | IntPtr ptr = Allocator.Allocate(Size);
27 | GC.AddMemoryPressure(Size);
28 |
29 | Marshal.StructureToPtr(structure, ptr, false);
30 | Data = ptr.ToPointer();
31 |
32 | _valid = Data != null;
33 | _zeroOnFree = zeroOnFree;
34 | }
35 |
36 | private void Free()
37 | {
38 | lock (_disposeLock)
39 | {
40 | if (!_valid)
41 | return;
42 | IntPtr ptr = new(Data);
43 |
44 | Marshal.DestroyStructure(ptr);
45 | if (_zeroOnFree)
46 | new Span(Data, Size).Clear();
47 | Allocator.Free(ptr);
48 | GC.RemoveMemoryPressure(Size);
49 |
50 | _valid = false;
51 | }
52 | }
53 |
54 | public void Dispose()
55 | {
56 | Free();
57 | GC.SuppressFinalize(this);
58 | }
59 |
60 | ~NativeStruct()
61 | {
62 | Free();
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Poltergeist.Core/Memory/PointerUtils.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace Poltergeist.Core.Memory
4 | {
5 | public static unsafe class PointerUtils
6 | {
7 | [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
8 | public static bool IsReadable(T* ptr) where T : unmanaged
9 | {
10 | try
11 | {
12 | _ = *ptr;
13 | return true;
14 | }
15 | catch
16 | {
17 | return false;
18 | }
19 | }
20 | [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
21 | public static bool IsReadable(T** ptr) where T : unmanaged
22 | {
23 | try
24 | {
25 | _ = *ptr;
26 | return true;
27 | }
28 | catch
29 | {
30 | return false;
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Poltergeist.Core/Poltergeist.Core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net5.0
5 | 9
6 | true
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | <_Parameter1>$(AssemblyName).Tests
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Poltergeist.GameKit.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30907.101
5 | MinimumVisualStudioVersion = 15.0.26124.0
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Poltergeist.Core", "Poltergeist.Core\Poltergeist.Core.csproj", "{796E38FA-5EC0-4BDA-A0D0-D966D7D58348}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Poltergeist.Core.Tests", "Poltergeist.Core.Tests\Poltergeist.Core.Tests.csproj", "{69DA7EB8-C89F-487F-9E00-4EEA1FC17C72}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FACD440C-DD6D-46A7-83AA-7B3D803FEE45}"
11 | ProjectSection(SolutionItems) = preProject
12 | .editorconfig = .editorconfig
13 | EndProjectSection
14 | EndProject
15 | Global
16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
17 | Debug|x64 = Debug|x64
18 | Release|x64 = Release|x64
19 | EndGlobalSection
20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
21 | {796E38FA-5EC0-4BDA-A0D0-D966D7D58348}.Debug|x64.ActiveCfg = Debug|Any CPU
22 | {796E38FA-5EC0-4BDA-A0D0-D966D7D58348}.Debug|x64.Build.0 = Debug|Any CPU
23 | {796E38FA-5EC0-4BDA-A0D0-D966D7D58348}.Release|x64.ActiveCfg = Release|Any CPU
24 | {796E38FA-5EC0-4BDA-A0D0-D966D7D58348}.Release|x64.Build.0 = Release|Any CPU
25 | {69DA7EB8-C89F-487F-9E00-4EEA1FC17C72}.Debug|x64.ActiveCfg = Debug|Any CPU
26 | {69DA7EB8-C89F-487F-9E00-4EEA1FC17C72}.Debug|x64.Build.0 = Debug|Any CPU
27 | {69DA7EB8-C89F-487F-9E00-4EEA1FC17C72}.Release|x64.ActiveCfg = Release|Any CPU
28 | {69DA7EB8-C89F-487F-9E00-4EEA1FC17C72}.Release|x64.Build.0 = Release|Any CPU
29 | EndGlobalSection
30 | GlobalSection(SolutionProperties) = preSolution
31 | HideSolutionNode = FALSE
32 | EndGlobalSection
33 | GlobalSection(ExtensibilityGlobals) = postSolution
34 | SolutionGuid = {EF765CE4-A8E7-44C4-89AF-A52CB2C39625}
35 | EndGlobalSection
36 | EndGlobal
37 |
--------------------------------------------------------------------------------
/PoltergeistEditor/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | add_executable(PoltergeistEditor src/PoltergeistEditor.cpp src/ImGUIContent.cpp)
2 | target_include_directories(PoltergeistEditor PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include/")
3 | find_package(glfw3 CONFIG REQUIRED)
4 | find_package(glad CONFIG REQUIRED)
5 | find_package(imgui CONFIG REQUIRED)
6 | target_link_libraries(PoltergeistEditor PUBLIC Poltergeist::PoltergeistEngine PRIVATE glfw glad::glad imgui::imgui)
7 | add_executable(Poltergeist::PoltergeistEditor ALIAS PoltergeistEditor)
8 | add_custom_target(copy-shaders-to-editor ALL COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/PoltergeistEngine/shaders ${CMAKE_CURRENT_BINARY_DIR})
9 | add_custom_target(copy-resources-to-editor ALL COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/PoltergeistEngine/resources ${CMAKE_CURRENT_BINARY_DIR})
10 | add_dependencies(PoltergeistEditor copy-shaders-to-editor copy-resources-to-editor)
11 |
--------------------------------------------------------------------------------
/PoltergeistEditor/include/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProjectPoltergeist/Poltergeist/ecdcadd3d9dfe537e588dc984e290a0dd71a7f6c/PoltergeistEditor/include/.gitkeep
--------------------------------------------------------------------------------
/PoltergeistEditor/include/ImGUIContent.hpp:
--------------------------------------------------------------------------------
1 | #ifndef POLTERGEIST_IMGUICONTENT_HPP
2 | #define POLTERGEIST_IMGUICONTENT_HPP
3 |
4 | void GUIContent();
5 |
6 | #endif
7 |
--------------------------------------------------------------------------------
/PoltergeistEditor/src/ImGUIContent.cpp:
--------------------------------------------------------------------------------
1 | #include "ImGUIContent.hpp"
2 | #include
3 |
4 | void GUIContent()
5 | {
6 | ImGui::BeginMainMenuBar();
7 | ImGui::EndMainMenuBar();
8 | ImGui::ShowDemoWindow();
9 | }
10 |
--------------------------------------------------------------------------------
/PoltergeistEditor/src/PoltergeistEditor.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include "ImGUIContent.hpp"
9 | #include
10 | #ifdef WIN32
11 | #include
12 | #endif
13 |
14 | extern "C"
15 | {
16 | POLTERGEIST_PUBLIC uint32_t NvOptimusEnablement = 1;
17 | POLTERGEIST_PUBLIC int32_t AmdPowerXpressRequestHighPerformance = 1;
18 | }
19 |
20 | void OnWindowSizeUpdate(GLFWwindow* window, int width, int height)
21 | {
22 | glViewport(0, 0, width, height);
23 | }
24 |
25 | int main()
26 | {
27 | #ifdef WIN32
28 | SetConsoleOutputCP(CP_UTF8);
29 | setvbuf(stdout, nullptr, _IOFBF, 1024);
30 | #endif
31 |
32 | std::cout << "Hello editor!" << std::endl;
33 |
34 | glfwInit();
35 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
36 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
37 | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
38 | glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
39 |
40 | {
41 | std::unique_ptr window {
42 | glfwCreateWindow(800, 600, "Poltergeist Editor", nullptr, nullptr),
43 | &glfwDestroyWindow
44 | };
45 |
46 | if (window.get() == nullptr)
47 | {
48 | glfwTerminate();
49 | return -1;
50 | }
51 |
52 | glfwMakeContextCurrent(window.get());
53 | glfwSetWindowSizeCallback(window.get(), OnWindowSizeUpdate);
54 |
55 | if (!gladLoadGLLoader(reinterpret_cast(glfwGetProcAddress)))
56 | {
57 | glfwTerminate();
58 | return -2;
59 | }
60 |
61 | IMGUI_CHECKVERSION();
62 |
63 | ImGui::CreateContext();
64 | ImGui::StyleColorsDark();
65 | ImGui_ImplGlfw_InitForOpenGL(window.get(), true);
66 | ImGui_ImplOpenGL3_Init();
67 |
68 | while (!glfwWindowShouldClose(window.get()))
69 | {
70 | glfwPollEvents();
71 |
72 | ImGui_ImplOpenGL3_NewFrame();
73 | ImGui_ImplGlfw_NewFrame();
74 | ImGui::NewFrame();
75 |
76 | GUIContent();
77 | ImGui::Render();
78 |
79 | ImVec4 backgroundColor(1.0f, 1.0f, 1.0f, 1.0f);
80 | glClearColor(backgroundColor.x, backgroundColor.y, backgroundColor.z, backgroundColor.w);
81 | glClear(GL_COLOR_BUFFER_BIT);
82 |
83 | ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
84 |
85 | glfwSwapBuffers(window.get());
86 | }
87 | }
88 |
89 | ImGui_ImplOpenGL3_Shutdown();
90 | ImGui_ImplGlfw_Shutdown();
91 | ImGui::DestroyContext();
92 |
93 | glfwTerminate();
94 |
95 | return 0;
96 | }
97 |
--------------------------------------------------------------------------------
/PoltergeistEngine/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | add_library(PoltergeistEngine STATIC src/PoltergeistEngine/Rendering/VertexArray.cpp src/PoltergeistEngine/Rendering/IndexBuffer.cpp src/PoltergeistEngine/Rendering/VertexBuffer.cpp src/PoltergeistEngine/Rendering/Shader.cpp src/PoltergeistEngine/Rendering/ShaderStage.cpp src/PoltergeistEngine/Rendering/Texture.cpp src/PoltergeistEngine/Image/Image.cpp src/PoltergeistEngine/Rendering/Renderer.cpp src/PoltergeistEngine/Encoding/EncodingUtilities.cpp src/PoltergeistEngine/IO/FileUtilities.cpp src/PoltergeistEngine/Rendering/FrameBuffer.cpp)
2 | target_include_directories(PoltergeistEngine PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include/")
3 | find_package(glad CONFIG REQUIRED)
4 | find_package(glm CONFIG REQUIRED)
5 | find_package(libpng CONFIG REQUIRED)
6 | target_link_libraries(PoltergeistEngine PRIVATE glad::glad glm::glm png)
7 | add_library(Poltergeist::PoltergeistEngine ALIAS PoltergeistEngine)
8 |
--------------------------------------------------------------------------------
/PoltergeistEngine/include/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProjectPoltergeist/Poltergeist/ecdcadd3d9dfe537e588dc984e290a0dd71a7f6c/PoltergeistEngine/include/.gitkeep
--------------------------------------------------------------------------------
/PoltergeistEngine/include/PoltergeistEngine/Encoding/EncodingUtilities.hpp:
--------------------------------------------------------------------------------
1 | #ifndef POLTERGEIST_ENCODINGUTILITIES_HPP
2 | #define POLTERGEIST_ENCODINGUTILITIES_HPP
3 |
4 | #ifdef WIN32
5 | wchar_t* ConvertUtf8ToUtf16(const char* data);
6 | char* ConvertUtf16ToUtf8(const wchar_t* data);
7 | #endif
8 |
9 | #endif
10 |
--------------------------------------------------------------------------------
/PoltergeistEngine/include/PoltergeistEngine/IO/FileUtilities.hpp:
--------------------------------------------------------------------------------
1 | #ifndef POLTERGEIST_FILEUTILITIES_HPP
2 | #define POLTERGEIST_FILEUTILITIES_HPP
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | FILE* OpenFile(const char* filename, const char* mode);
9 |
10 | [[nodiscard]] static std::optional GetFileContent(const std::filesystem::path& filePath)
11 | {
12 | std::ifstream fileStream(filePath);
13 |
14 | if (fileStream.good())
15 | {
16 | auto result = std::make_optional();
17 |
18 | fileStream.seekg(0, std::ios::end);
19 | result->resize(fileStream.tellg());
20 | fileStream.seekg(0, std::ios::beg);
21 | fileStream.read(result->data(), result->size());
22 |
23 | return result;
24 | }
25 |
26 | return {};
27 | }
28 |
29 | #endif
30 |
--------------------------------------------------------------------------------
/PoltergeistEngine/include/PoltergeistEngine/Image/Image.hpp:
--------------------------------------------------------------------------------
1 | #ifndef POLTERGEIST_IMAGE_HPP
2 | #define POLTERGEIST_IMAGE_HPP
3 |
4 | #include
5 | #include
6 |
7 | class Image
8 | {
9 | private:
10 | uint32_t m_width = 0, m_height = 0;
11 | uint8_t* m_data = nullptr;
12 | public:
13 | explicit Image(const std::filesystem::path &imagePath);
14 | ~Image() noexcept;
15 |
16 | uint32_t GetWidth() const noexcept;
17 | uint32_t GetHeight() const noexcept;
18 | uint8_t* GetData() const noexcept;
19 | };
20 |
21 | #endif
22 |
--------------------------------------------------------------------------------
/PoltergeistEngine/include/PoltergeistEngine/Macros.hpp:
--------------------------------------------------------------------------------
1 | #ifndef POLTERGEIST_MACROS_HPP
2 | #define POLTERGEIST_MACROS_HPP
3 |
4 | #ifdef POLTERGEIST_ENGINE_EXPORTS
5 | #endif
6 | #if defined _WIN32 || defined WIN32 || defined __CYGWIN__
7 | #ifdef _MSC_VER
8 | #define POLTERGEIST_PUBLIC __declspec(dllexport)
9 | #else
10 | #define POLTERGEIST_PUBLIC __attribute__ ((dllexport))
11 | #endif
12 | #define POLTERGEIST_INTERNAL
13 | #else
14 | #if __clang_major__ >= 3 || __GNUC__ >= 4
15 | #define POLTERGEIST_PUBLIC __attribute__ ((visibility ("default")))
16 | #define POLTERGEIST_INTERNAL __attribute__ ((visibility ("hidden")))
17 | #else
18 | #define POLTERGEIST_PUBLIC
19 | #define POLTERGEIST_INTERNAL
20 | #endif
21 | #endif
22 |
23 | #endif
24 |
--------------------------------------------------------------------------------
/PoltergeistEngine/include/PoltergeistEngine/Rendering/FrameBuffer.hpp:
--------------------------------------------------------------------------------
1 | #ifndef POLTERGEIST_FRAMEBUFFER_HPP
2 | #define POLTERGEIST_FRAMEBUFFER_HPP
3 |
4 | #include
5 | #include
6 | #include "PoltergeistEngine/Rendering/Texture.hpp"
7 |
8 | class FrameBuffer
9 | {
10 | private:
11 | uint32_t m_frameBufferId;
12 | std::shared_ptr m_textureAttachment;
13 | uint32_t m_depthAndStencilAttachmentId;
14 | public:
15 | FrameBuffer(uint32_t width, uint32_t height);
16 | ~FrameBuffer() noexcept;
17 |
18 | void Bind() const noexcept;
19 | void Unbind() const noexcept;
20 |
21 | std::shared_ptr GetTextureAttachment() const noexcept;
22 | };
23 |
24 | #endif
25 |
--------------------------------------------------------------------------------
/PoltergeistEngine/include/PoltergeistEngine/Rendering/IndexBuffer.hpp:
--------------------------------------------------------------------------------
1 | #ifndef POLTERGEIST_INDEXBUFFER_HPP
2 | #define POLTERGEIST_INDEXBUFFER_HPP
3 |
4 | #include
5 | #include