From 82b4f23c06c3c3cea66ff48f14fea34dc9d37151 Mon Sep 17 00:00:00 2001 From: Signal Date: Sun, 22 Feb 2026 17:45:44 -0500 Subject: [PATCH] Initial commit. --- ArtifactEngine.xcodeproj/project.pbxproj | 828 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + CMakeLists.txt | 20 + Client/CMakeLists.txt | 6 + Client/Client.cpp | 56 ++ Client/Client.h | 40 + Client/Graphics/Camera.cpp | 7 + Client/Graphics/Camera.h | 24 + Client/Graphics/ChunkRenderer.cpp | 1 + Client/Graphics/ChunkRenderer.h | 22 + Client/Graphics/EntityRenderer.cpp | 7 + Client/Graphics/EntityRenderer.h | 12 + Client/Graphics/Graphics.cpp | 168 ++++ Client/Graphics/Graphics.h | 43 + Client/Graphics/UIRenderer.cpp | 429 +++++++++ Client/Graphics/UIRenderer.h | 46 + Client/Graphics/WorldRenderer.cpp | 1 + Client/Graphics/WorldRenderer.h | 11 + Client/LocalPlayer.cpp | 17 + Client/LocalPlayer.h | 26 + Client/Network.h | 11 + Client/Platform/Keybinds.cpp | 12 + Client/Platform/Keybinds.h | 13 + Client/Platform/Window.cpp | 177 ++++ Client/Platform/Window.h | 103 +++ Client/glfw3webgpu/.gitignore | 1 + Client/glfw3webgpu/CMakeLists.txt | 15 + Client/glfw3webgpu/LICENSE.txt | 20 + Client/glfw3webgpu/README.md | 94 ++ Client/glfw3webgpu/glfw3webgpu.c | 191 ++++ Client/glfw3webgpu/glfw3webgpu.h | 62 ++ README.md | 12 + Server/CMakeLists.txt | 6 + Server/Network.h | 10 + Server/Player.cpp | 7 + Server/Player.h | 9 + Server/Server.cpp | 37 + Server/Server.h | 28 + Server/World/WorldBackend.cpp | 7 + Server/World/WorldBackend.h | 10 + Server/World/WorldManager.cpp | 11 + Server/World/WorldManager.h | 16 + Shared/CMakeLists.txt | 6 + Shared/Events.h | 91 ++ Shared/Network/Connection.cpp | 148 ++++ Shared/Network/Connection.h | 95 ++ Shared/Network/Network.cpp | 31 + Shared/Network/Network.h | 45 + Shared/Paths.cpp | 60 ++ Shared/Paths.h | 24 + Shared/Settings.cpp | 58 ++ Shared/Settings.h | 85 ++ Shared/Shared.cpp | 11 + Shared/Shared.h | 147 ++++ Shared/World/Chunk.h | 9 + TestGame/main.cpp | 52 ++ 56 files changed, 3485 insertions(+) create mode 100644 ArtifactEngine.xcodeproj/project.pbxproj create mode 100644 ArtifactEngine.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 CMakeLists.txt create mode 100644 Client/CMakeLists.txt create mode 100644 Client/Client.cpp create mode 100644 Client/Client.h create mode 100644 Client/Graphics/Camera.cpp create mode 100644 Client/Graphics/Camera.h create mode 100644 Client/Graphics/ChunkRenderer.cpp create mode 100644 Client/Graphics/ChunkRenderer.h create mode 100644 Client/Graphics/EntityRenderer.cpp create mode 100644 Client/Graphics/EntityRenderer.h create mode 100644 Client/Graphics/Graphics.cpp create mode 100644 Client/Graphics/Graphics.h create mode 100644 Client/Graphics/UIRenderer.cpp create mode 100644 Client/Graphics/UIRenderer.h create mode 100644 Client/Graphics/WorldRenderer.cpp create mode 100644 Client/Graphics/WorldRenderer.h create mode 100644 Client/LocalPlayer.cpp create mode 100644 Client/LocalPlayer.h create mode 100644 Client/Network.h create mode 100644 Client/Platform/Keybinds.cpp create mode 100644 Client/Platform/Keybinds.h create mode 100644 Client/Platform/Window.cpp create mode 100644 Client/Platform/Window.h create mode 100644 Client/glfw3webgpu/.gitignore create mode 100644 Client/glfw3webgpu/CMakeLists.txt create mode 100644 Client/glfw3webgpu/LICENSE.txt create mode 100644 Client/glfw3webgpu/README.md create mode 100644 Client/glfw3webgpu/glfw3webgpu.c create mode 100644 Client/glfw3webgpu/glfw3webgpu.h create mode 100644 README.md create mode 100644 Server/CMakeLists.txt create mode 100644 Server/Network.h create mode 100644 Server/Player.cpp create mode 100644 Server/Player.h create mode 100644 Server/Server.cpp create mode 100644 Server/Server.h create mode 100644 Server/World/WorldBackend.cpp create mode 100644 Server/World/WorldBackend.h create mode 100644 Server/World/WorldManager.cpp create mode 100644 Server/World/WorldManager.h create mode 100644 Shared/CMakeLists.txt create mode 100644 Shared/Events.h create mode 100644 Shared/Network/Connection.cpp create mode 100644 Shared/Network/Connection.h create mode 100644 Shared/Network/Network.cpp create mode 100644 Shared/Network/Network.h create mode 100644 Shared/Paths.cpp create mode 100644 Shared/Paths.h create mode 100644 Shared/Settings.cpp create mode 100644 Shared/Settings.h create mode 100644 Shared/Shared.cpp create mode 100644 Shared/Shared.h create mode 100644 Shared/World/Chunk.h create mode 100644 TestGame/main.cpp diff --git a/ArtifactEngine.xcodeproj/project.pbxproj b/ArtifactEngine.xcodeproj/project.pbxproj new file mode 100644 index 0000000..ceed15a --- /dev/null +++ b/ArtifactEngine.xcodeproj/project.pbxproj @@ -0,0 +1,828 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 9B20EE962F4BC69100117DD8 /* libwgpu_native.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EE952F4BC69100117DD8 /* libwgpu_native.a */; }; + 9B20EE982F4BC6C600117DD8 /* libngtcp2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EE972F4BC6C600117DD8 /* libngtcp2.a */; }; + 9B20EE9C2F4BCF7D00117DD8 /* libglfw3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EE9B2F4BCF7D00117DD8 /* libglfw3.a */; }; + 9B20EEAA2F4BD04000117DD8 /* libClient.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EE672F4BC09C00117DD8 /* libClient.a */; }; + 9B20EEAB2F4BD04400117DD8 /* libServer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EE752F4BC0FD00117DD8 /* libServer.a */; }; + 9B20EEAC2F4BD04400117DD8 /* libShared.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EE7F2F4BC11100117DD8 /* libShared.a */; }; + 9B20EEAD2F4BD05300117DD8 /* libShared.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EE7F2F4BC11100117DD8 /* libShared.a */; }; + 9B20EEAE2F4BD05700117DD8 /* libShared.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EE7F2F4BC11100117DD8 /* libShared.a */; }; + 9B20EEC22F4D028400117DD8 /* libgnutls.30.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EEC12F4D028400117DD8 /* libgnutls.30.dylib */; }; + 9B20EEC42F4D141600117DD8 /* libev.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EEC32F4D141600117DD8 /* libev.a */; }; + 9B20EEDA2F60D87E00117DD8 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EED92F60D87E00117DD8 /* IOKit.framework */; }; + 9B20EEDC2F60D88D00117DD8 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EEDB2F60D88D00117DD8 /* QuartzCore.framework */; }; + 9B20EEE02F60D92300117DD8 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EEDF2F60D92300117DD8 /* Cocoa.framework */; }; + 9B20EF182F60F1DF00117DD8 /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EF172F60F1DF00117DD8 /* CoreVideo.framework */; }; + 9B20EF192F60F1E500117DD8 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EEDB2F60D88D00117DD8 /* QuartzCore.framework */; }; + 9B20EF1A2F60F1F000117DD8 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EEDF2F60D92300117DD8 /* Cocoa.framework */; }; + 9B20EF1C2F60F22500117DD8 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EED92F60D87E00117DD8 /* IOKit.framework */; }; + 9B20EF1D2F60F23C00117DD8 /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EF172F60F1DF00117DD8 /* CoreVideo.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 9B20EE8B2F4BC55500117DD8 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9B20EE292F4BBE9600117DD8 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9B20EE7E2F4BC11100117DD8; + remoteInfo = Shared; + }; + 9B20EE8D2F4BC55F00117DD8 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9B20EE292F4BBE9600117DD8 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9B20EE7E2F4BC11100117DD8; + remoteInfo = Shared; + }; + 9B20EE8F2F4BC56500117DD8 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9B20EE292F4BBE9600117DD8 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9B20EE662F4BC09C00117DD8; + remoteInfo = Client; + }; + 9B20EE912F4BC56500117DD8 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9B20EE292F4BBE9600117DD8 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9B20EE742F4BC0FD00117DD8; + remoteInfo = Server; + }; + 9B20EE932F4BC56500117DD8 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9B20EE292F4BBE9600117DD8 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9B20EE7E2F4BC11100117DD8; + remoteInfo = Shared; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9B20EE582F4BBF4800117DD8 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 9B20EE3E2F4BBEEC00117DD8 /* CMakeLists.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; + 9B20EE5A2F4BBF4800117DD8 /* TestGame */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = TestGame; sourceTree = BUILT_PRODUCTS_DIR; }; + 9B20EE672F4BC09C00117DD8 /* libClient.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libClient.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 9B20EE752F4BC0FD00117DD8 /* libServer.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libServer.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 9B20EE7F2F4BC11100117DD8 /* libShared.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libShared.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 9B20EE952F4BC69100117DD8 /* libwgpu_native.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libwgpu_native.a; path = "../../../eclipse-workspace/ArtifactEngine/deps/webgpu/lib/libwgpu_native.a"; sourceTree = ""; }; + 9B20EE972F4BC6C600117DD8 /* libngtcp2.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libngtcp2.a; path = "../../../eclipse-workspace/ArtifactEngine/deps/ngtcp2/build/lib/libngtcp2.a"; sourceTree = ""; }; + 9B20EE9B2F4BCF7D00117DD8 /* libglfw3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libglfw3.a; path = "../../../eclipse-workspace/ArtifactEngine/deps/glfw/install/lib/libglfw3.a"; sourceTree = ""; }; + 9B20EEC12F4D028400117DD8 /* libgnutls.30.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libgnutls.30.dylib; path = ../../../homebrew/Cellar/gnutls/3.8.11/lib/libgnutls.30.dylib; sourceTree = ""; }; + 9B20EEC32F4D141600117DD8 /* libev.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libev.a; path = ../../../homebrew/Cellar/libev/4.33/lib/libev.a; sourceTree = ""; }; + 9B20EED42F60D84B00117DD8 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + 9B20EED72F60D86C00117DD8 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 9B20EED92F60D87E00117DD8 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; + 9B20EEDB2F60D88D00117DD8 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + 9B20EEDD2F60D89F00117DD8 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 9B20EEDF2F60D92300117DD8 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; + 9B20EEE12F60D93200117DD8 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; + 9B20EF172F60F1DF00117DD8 /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = System/Library/Frameworks/CoreVideo.framework; sourceTree = SDKROOT; }; + 9BE79E5E2F846F6D0038B496 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 9B20EE6C2F4BC0C500117DD8 /* Exceptions for "Client" folder in "Client" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + additionalCompilerFlagsByRelativePath = { + glfw3webgpu/glfw3webgpu.c = "-x objective-c"; + }; + membershipExceptions = ( + Client.cpp, + Client.h, + glfw3webgpu/glfw3webgpu.c, + glfw3webgpu/glfw3webgpu.h, + Graphics/Camera.cpp, + Graphics/Camera.h, + Graphics/ChunkRenderer.cpp, + Graphics/EntityRenderer.cpp, + Graphics/EntityRenderer.h, + Graphics/Graphics.cpp, + Graphics/Graphics.h, + Graphics/UIRenderer.cpp, + Graphics/UIRenderer.h, + Graphics/WorldRenderer.cpp, + Graphics/WorldRenderer.h, + LocalPlayer.cpp, + LocalPlayer.h, + Network.h, + Platform/Keybinds.cpp, + Platform/Keybinds.h, + Platform/Window.cpp, + Platform/Window.h, + ); + target = 9B20EE662F4BC09C00117DD8 /* Client */; + }; + 9B20EE7A2F4BC10500117DD8 /* Exceptions for "Server" folder in "Server" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Network.h, + Player.cpp, + Player.h, + Server.cpp, + Server.h, + World/WorldBackend.cpp, + World/WorldManager.cpp, + ); + target = 9B20EE742F4BC0FD00117DD8 /* Server */; + }; + 9B20EE842F4BC11700117DD8 /* Exceptions for "Shared" folder in "Shared" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Events.h, + Network/Connection.cpp, + Network/Network.cpp, + Network/Network.h, + Paths.cpp, + Paths.h, + Settings.cpp, + Settings.h, + Shared.cpp, + Shared.h, + ); + target = 9B20EE7E2F4BC11100117DD8 /* Shared */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 9B20EE4B2F4BBF0200117DD8 /* Client */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 9B20EE6C2F4BC0C500117DD8 /* Exceptions for "Client" folder in "Client" target */, + ); + path = Client; + sourceTree = ""; + }; + 9B20EE522F4BBF0A00117DD8 /* Server */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 9B20EE7A2F4BC10500117DD8 /* Exceptions for "Server" folder in "Server" target */, + ); + path = Server; + sourceTree = ""; + }; + 9B20EE552F4BBF0D00117DD8 /* Shared */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 9B20EE842F4BC11700117DD8 /* Exceptions for "Shared" folder in "Shared" target */, + ); + path = Shared; + sourceTree = ""; + }; + 9B20EE5B2F4BBF4800117DD8 /* TestGame */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = TestGame; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 9B20EE572F4BBF4800117DD8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9B20EF1D2F60F23C00117DD8 /* CoreVideo.framework in Frameworks */, + 9B20EEE02F60D92300117DD8 /* Cocoa.framework in Frameworks */, + 9B20EEDC2F60D88D00117DD8 /* QuartzCore.framework in Frameworks */, + 9B20EEDA2F60D87E00117DD8 /* IOKit.framework in Frameworks */, + 9B20EEAB2F4BD04400117DD8 /* libServer.a in Frameworks */, + 9B20EEAC2F4BD04400117DD8 /* libShared.a in Frameworks */, + 9B20EEAA2F4BD04000117DD8 /* libClient.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9B20EE652F4BC09C00117DD8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9B20EF1C2F60F22500117DD8 /* IOKit.framework in Frameworks */, + 9B20EF1A2F60F1F000117DD8 /* Cocoa.framework in Frameworks */, + 9B20EF192F60F1E500117DD8 /* QuartzCore.framework in Frameworks */, + 9B20EF182F60F1DF00117DD8 /* CoreVideo.framework in Frameworks */, + 9B20EEAD2F4BD05300117DD8 /* libShared.a in Frameworks */, + 9B20EE9C2F4BCF7D00117DD8 /* libglfw3.a in Frameworks */, + 9B20EE962F4BC69100117DD8 /* libwgpu_native.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9B20EE732F4BC0FD00117DD8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9B20EEAE2F4BD05700117DD8 /* libShared.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9B20EE7D2F4BC11100117DD8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9B20EEC42F4D141600117DD8 /* libev.a in Frameworks */, + 9B20EEC22F4D028400117DD8 /* libgnutls.30.dylib in Frameworks */, + 9B20EE982F4BC6C600117DD8 /* libngtcp2.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9B20EE282F4BBE9600117DD8 = { + isa = PBXGroup; + children = ( + 9BE79E5E2F846F6D0038B496 /* README.md */, + 9B20EE3E2F4BBEEC00117DD8 /* CMakeLists.txt */, + 9B20EE4B2F4BBF0200117DD8 /* Client */, + 9B20EE522F4BBF0A00117DD8 /* Server */, + 9B20EE552F4BBF0D00117DD8 /* Shared */, + 9B20EE5B2F4BBF4800117DD8 /* TestGame */, + 9B20EE852F4BC12400117DD8 /* Frameworks */, + 9B20EE322F4BBE9600117DD8 /* Products */, + ); + sourceTree = ""; + }; + 9B20EE322F4BBE9600117DD8 /* Products */ = { + isa = PBXGroup; + children = ( + 9B20EE5A2F4BBF4800117DD8 /* TestGame */, + 9B20EE672F4BC09C00117DD8 /* libClient.a */, + 9B20EE752F4BC0FD00117DD8 /* libServer.a */, + 9B20EE7F2F4BC11100117DD8 /* libShared.a */, + ); + name = Products; + sourceTree = ""; + }; + 9B20EE852F4BC12400117DD8 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 9B20EF172F60F1DF00117DD8 /* CoreVideo.framework */, + 9B20EEE12F60D93200117DD8 /* Metal.framework */, + 9B20EEDF2F60D92300117DD8 /* Cocoa.framework */, + 9B20EEDD2F60D89F00117DD8 /* Foundation.framework */, + 9B20EEDB2F60D88D00117DD8 /* QuartzCore.framework */, + 9B20EED92F60D87E00117DD8 /* IOKit.framework */, + 9B20EED72F60D86C00117DD8 /* CoreGraphics.framework */, + 9B20EED42F60D84B00117DD8 /* CoreFoundation.framework */, + 9B20EEC32F4D141600117DD8 /* libev.a */, + 9B20EEC12F4D028400117DD8 /* libgnutls.30.dylib */, + 9B20EE9B2F4BCF7D00117DD8 /* libglfw3.a */, + 9B20EE972F4BC6C600117DD8 /* libngtcp2.a */, + 9B20EE952F4BC69100117DD8 /* libwgpu_native.a */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 9B20EE632F4BC09C00117DD8 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9B20EE712F4BC0FD00117DD8 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9B20EE7B2F4BC11100117DD8 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 9B20EE592F4BBF4800117DD8 /* TestGame */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9B20EE5E2F4BBF4800117DD8 /* Build configuration list for PBXNativeTarget "TestGame" */; + buildPhases = ( + 9B20EE562F4BBF4800117DD8 /* Sources */, + 9B20EE572F4BBF4800117DD8 /* Frameworks */, + 9B20EE582F4BBF4800117DD8 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + 9B20EE902F4BC56500117DD8 /* PBXTargetDependency */, + 9B20EE922F4BC56500117DD8 /* PBXTargetDependency */, + 9B20EE942F4BC56500117DD8 /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + 9B20EE5B2F4BBF4800117DD8 /* TestGame */, + ); + name = TestGame; + packageProductDependencies = ( + ); + productName = TestGame; + productReference = 9B20EE5A2F4BBF4800117DD8 /* TestGame */; + productType = "com.apple.product-type.tool"; + }; + 9B20EE662F4BC09C00117DD8 /* Client */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9B20EE682F4BC09C00117DD8 /* Build configuration list for PBXNativeTarget "Client" */; + buildPhases = ( + 9B20EE632F4BC09C00117DD8 /* Headers */, + 9B20EE642F4BC09C00117DD8 /* Sources */, + 9B20EE652F4BC09C00117DD8 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 9B20EE8E2F4BC55F00117DD8 /* PBXTargetDependency */, + ); + name = Client; + packageProductDependencies = ( + ); + productName = Client; + productReference = 9B20EE672F4BC09C00117DD8 /* libClient.a */; + productType = "com.apple.product-type.library.static"; + }; + 9B20EE742F4BC0FD00117DD8 /* Server */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9B20EE762F4BC0FD00117DD8 /* Build configuration list for PBXNativeTarget "Server" */; + buildPhases = ( + 9B20EE712F4BC0FD00117DD8 /* Headers */, + 9B20EE722F4BC0FD00117DD8 /* Sources */, + 9B20EE732F4BC0FD00117DD8 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 9B20EE8C2F4BC55500117DD8 /* PBXTargetDependency */, + ); + name = Server; + packageProductDependencies = ( + ); + productName = Server; + productReference = 9B20EE752F4BC0FD00117DD8 /* libServer.a */; + productType = "com.apple.product-type.library.static"; + }; + 9B20EE7E2F4BC11100117DD8 /* Shared */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9B20EE802F4BC11100117DD8 /* Build configuration list for PBXNativeTarget "Shared" */; + buildPhases = ( + 9B20EE7B2F4BC11100117DD8 /* Headers */, + 9B20EE7C2F4BC11100117DD8 /* Sources */, + 9B20EE7D2F4BC11100117DD8 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Shared; + packageProductDependencies = ( + ); + productName = Shared; + productReference = 9B20EE7F2F4BC11100117DD8 /* libShared.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 9B20EE292F4BBE9600117DD8 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastUpgradeCheck = 1640; + TargetAttributes = { + 9B20EE592F4BBF4800117DD8 = { + CreatedOnToolsVersion = 16.4; + }; + 9B20EE662F4BC09C00117DD8 = { + CreatedOnToolsVersion = 16.4; + }; + 9B20EE742F4BC0FD00117DD8 = { + CreatedOnToolsVersion = 16.4; + }; + 9B20EE7E2F4BC11100117DD8 = { + CreatedOnToolsVersion = 16.4; + }; + }; + }; + buildConfigurationList = 9B20EE2C2F4BBE9600117DD8 /* Build configuration list for PBXProject "ArtifactEngine" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 9B20EE282F4BBE9600117DD8; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = 9B20EE322F4BBE9600117DD8 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 9B20EE592F4BBF4800117DD8 /* TestGame */, + 9B20EE662F4BC09C00117DD8 /* Client */, + 9B20EE742F4BC0FD00117DD8 /* Server */, + 9B20EE7E2F4BC11100117DD8 /* Shared */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 9B20EE562F4BBF4800117DD8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9B20EE642F4BC09C00117DD8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9B20EE722F4BC0FD00117DD8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9B20EE7C2F4BC11100117DD8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 9B20EE8C2F4BC55500117DD8 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9B20EE7E2F4BC11100117DD8 /* Shared */; + targetProxy = 9B20EE8B2F4BC55500117DD8 /* PBXContainerItemProxy */; + }; + 9B20EE8E2F4BC55F00117DD8 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9B20EE7E2F4BC11100117DD8 /* Shared */; + targetProxy = 9B20EE8D2F4BC55F00117DD8 /* PBXContainerItemProxy */; + }; + 9B20EE902F4BC56500117DD8 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9B20EE662F4BC09C00117DD8 /* Client */; + targetProxy = 9B20EE8F2F4BC56500117DD8 /* PBXContainerItemProxy */; + }; + 9B20EE922F4BC56500117DD8 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9B20EE742F4BC0FD00117DD8 /* Server */; + targetProxy = 9B20EE912F4BC56500117DD8 /* PBXContainerItemProxy */; + }; + 9B20EE942F4BC56500117DD8 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9B20EE7E2F4BC11100117DD8 /* Shared */; + targetProxy = 9B20EE932F4BC56500117DD8 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 9B20EE362F4BBE9600117DD8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 15.5; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + 9B20EE372F4BBE9600117DD8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 15.5; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + }; + name = Release; + }; + 9B20EE5F2F4BBF4800117DD8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + HEADER_SEARCH_PATHS = ( + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glfw/install/include", + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glm", + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/webgpu/include", + /Users/iboettcher/Desktop/misc/ArtifactEngine/Shared, + /Users/iboettcher/Desktop/misc/ArtifactEngine/Client, + /Users/iboettcher/Desktop/misc/ArtifactEngine/Server, + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/ngtcp2/install/include", + /Users/iboettcher/homebrew/include, + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 9B20EE602F4BBF4800117DD8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + HEADER_SEARCH_PATHS = ( + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glfw/install/include", + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glm", + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/webgpu/include", + /Users/iboettcher/Desktop/misc/ArtifactEngine/Shared, + /Users/iboettcher/Desktop/misc/ArtifactEngine/Client, + /Users/iboettcher/Desktop/misc/ArtifactEngine/Server, + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/ngtcp2/install/include", + /Users/iboettcher/homebrew/include, + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + 9B20EE692F4BC09C00117DD8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + EXECUTABLE_PREFIX = lib; + HEADER_SEARCH_PATHS = ( + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/webgpu/include", + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps", + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glfw/install/include", + /Users/iboettcher/Desktop/misc/ArtifactEngine/Shared, + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glm", + /Users/iboettcher/homebrew/include, + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/ngtcp2/install/include", + ); + LIBRARY_SEARCH_PATHS = ( + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glfw/install/lib", + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/webgpu/lib", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 9B20EE6A2F4BC09C00117DD8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + EXECUTABLE_PREFIX = lib; + HEADER_SEARCH_PATHS = ( + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/webgpu/include", + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps", + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glfw/install/include", + /Users/iboettcher/Desktop/misc/ArtifactEngine/Shared, + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glm", + /Users/iboettcher/homebrew/include, + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/ngtcp2/install/include", + ); + LIBRARY_SEARCH_PATHS = ( + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glfw/install/lib", + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/webgpu/lib", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; + 9B20EE772F4BC0FD00117DD8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + EXECUTABLE_PREFIX = lib; + HEADER_SEARCH_PATHS = ( + /Users/iboettcher/Desktop/misc/ArtifactEngine/Shared, + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glm", + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/ngtcp2/install/include", + /Users/iboettcher/homebrew/include, + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 9B20EE782F4BC0FD00117DD8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + EXECUTABLE_PREFIX = lib; + HEADER_SEARCH_PATHS = ( + /Users/iboettcher/Desktop/misc/ArtifactEngine/Shared, + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glm", + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/ngtcp2/install/include", + /Users/iboettcher/homebrew/include, + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; + 9B20EE812F4BC11100117DD8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + EXECUTABLE_PREFIX = lib; + HEADER_SEARCH_PATHS = ( + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/ngtcp2/install/include", + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps", + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glm", + /Users/iboettcher/homebrew/include, + ); + LIBRARY_SEARCH_PATHS = ( + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/ngtcp2/install/lib", + /Users/iboettcher/homebrew/lib, + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 9B20EE822F4BC11100117DD8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + EXECUTABLE_PREFIX = lib; + HEADER_SEARCH_PATHS = ( + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/ngtcp2/install/include", + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps", + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glm", + /Users/iboettcher/homebrew/include, + ); + LIBRARY_SEARCH_PATHS = ( + "/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/ngtcp2/install/lib", + /Users/iboettcher/homebrew/lib, + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 9B20EE2C2F4BBE9600117DD8 /* Build configuration list for PBXProject "ArtifactEngine" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9B20EE362F4BBE9600117DD8 /* Debug */, + 9B20EE372F4BBE9600117DD8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9B20EE5E2F4BBF4800117DD8 /* Build configuration list for PBXNativeTarget "TestGame" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9B20EE5F2F4BBF4800117DD8 /* Debug */, + 9B20EE602F4BBF4800117DD8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9B20EE682F4BC09C00117DD8 /* Build configuration list for PBXNativeTarget "Client" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9B20EE692F4BC09C00117DD8 /* Debug */, + 9B20EE6A2F4BC09C00117DD8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9B20EE762F4BC0FD00117DD8 /* Build configuration list for PBXNativeTarget "Server" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9B20EE772F4BC0FD00117DD8 /* Debug */, + 9B20EE782F4BC0FD00117DD8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9B20EE802F4BC11100117DD8 /* Build configuration list for PBXNativeTarget "Shared" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9B20EE812F4BC11100117DD8 /* Debug */, + 9B20EE822F4BC11100117DD8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 9B20EE292F4BBE9600117DD8 /* Project object */; +} diff --git a/ArtifactEngine.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ArtifactEngine.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/ArtifactEngine.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..013f891 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.20) +project(ArtifactEngine LANGUAGES CXX) + +# MARK: - Options + +option(BUILD_SHARED_LIBS "Build libraries as SHARED instead of STATIC" OFF) +option(BUILD_EXAMPLES "Build example/test applications" ON ) +option(BUILD_TESTS "Build unit tests" OFF) + +# MARK: - Libraries + +add_subdirectory(Shared) +add_subdirectory(Client) +add_subdirectory(Server) + +# MARK: - Test Game + +if(BUILD_EXAMPLES) + add_subdirectory(Examples/TestGame) +endif() \ No newline at end of file diff --git a/Client/CMakeLists.txt b/Client/CMakeLists.txt new file mode 100644 index 0000000..cb77098 --- /dev/null +++ b/Client/CMakeLists.txt @@ -0,0 +1,6 @@ +file(GLOB_RECURSE MY_SOURCES CONFIGURE_DEPENDS + "*.cpp" + "*.h" +) + +add_library(Client ${MY_SOURCES}) diff --git a/Client/Client.cpp b/Client/Client.cpp new file mode 100644 index 0000000..d21cb4e --- /dev/null +++ b/Client/Client.cpp @@ -0,0 +1,56 @@ +#include + +#include "Client.h" +#include "Network.h" +#include "Graphics/UIRenderer.h" +#include "Graphics/Graphics.h" +#include + +namespace Artifact { + +void Client::init() { + window = subsystem(); + + for (auto & system : subsystems) { + system->client = this; + system->init(); + system->reload(); + } +} + +void Client::render() { + for (auto & system : subsystems) { + system->render(); + } +} + +void Client::run() { + init(); + + auto time = std::chrono::steady_clock::now(); + while (!window->shouldClose()) { + auto now = std::chrono::steady_clock::now(); + if (time - now > std::chrono::milliseconds(50)) { + tick(); + } + + render(); + } +} + +void Client::tick() { + for (auto & system : subsystems) { + system->tick(); + } +} + +void Client::addDefaultSubsystems() { + addSubsystem(1080, 640, "Artifact Engine"); + addSubsystem(); + auto graphics = addSubsystem(); + { + graphics->addSubsystem(); + } +} + +} diff --git a/Client/Client.h b/Client/Client.h new file mode 100644 index 0000000..4e328d6 --- /dev/null +++ b/Client/Client.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#include "Shared.h" +#include + +namespace Artifact { + +class ClientSubsystem; +class WindowImpl; + +/// The client class. +class Client: public Engine { +public: + WindowImpl * window = nullptr; + Settings settings; + + Client() : settings(getClientConfigPath()) {} + + void addDefaultSubsystems(); + void initDefault(); + /// Initializes the client. This includes opening a window, creating a WebGPU device, etc. + void init(); + /// Runs a single iteration of the render loop. + void render(); + /// Runs a single iteration of the tick loop. + void tick(); + /// Runs the client. + void run(); +}; + +class ClientSubsystem: public BaseSubsystem { +public: + Client * client = nullptr; + virtual void render() {} + virtual void tick() {} +}; + +} diff --git a/Client/Graphics/Camera.cpp b/Client/Graphics/Camera.cpp new file mode 100644 index 0000000..35d440d --- /dev/null +++ b/Client/Graphics/Camera.cpp @@ -0,0 +1,7 @@ +#include "Camera.h" + +namespace Artifact { + + + +} diff --git a/Client/Graphics/Camera.h b/Client/Graphics/Camera.h new file mode 100644 index 0000000..c0088cd --- /dev/null +++ b/Client/Graphics/Camera.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +#include +#include + +namespace Artifact { + +class Camera { +public: + glm::vec3 pos; + glm::quat rot; + double fov; + float near; + float far; + + std::vector renderPasses; + + Camera(glm::vec3 pos, double fov, float near, float far) : pos(pos), fov(fov), near(near), far(far) {} +}; + +} diff --git a/Client/Graphics/ChunkRenderer.cpp b/Client/Graphics/ChunkRenderer.cpp new file mode 100644 index 0000000..d8718f9 --- /dev/null +++ b/Client/Graphics/ChunkRenderer.cpp @@ -0,0 +1 @@ +#include "ChunkRenderer.h" diff --git a/Client/Graphics/ChunkRenderer.h b/Client/Graphics/ChunkRenderer.h new file mode 100644 index 0000000..bf41d1e --- /dev/null +++ b/Client/Graphics/ChunkRenderer.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include + +#include +#include +#include "Graphics.h" + +namespace Artifact { + +class ChunkRenderer: public GraphicsSubsystem { + std::unordered_map chunks; + +public: + void render() { + + } +}; + +} diff --git a/Client/Graphics/EntityRenderer.cpp b/Client/Graphics/EntityRenderer.cpp new file mode 100644 index 0000000..e13acba --- /dev/null +++ b/Client/Graphics/EntityRenderer.cpp @@ -0,0 +1,7 @@ +#include "EntityRenderer.h" + +namespace Artifact { + + + +} diff --git a/Client/Graphics/EntityRenderer.h b/Client/Graphics/EntityRenderer.h new file mode 100644 index 0000000..9b3431a --- /dev/null +++ b/Client/Graphics/EntityRenderer.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include "Graphics.h" + +namespace Artifact { + +class EntityRenderer: public GraphicsSubsystem { + +}; + +} diff --git a/Client/Graphics/Graphics.cpp b/Client/Graphics/Graphics.cpp new file mode 100644 index 0000000..589580b --- /dev/null +++ b/Client/Graphics/Graphics.cpp @@ -0,0 +1,168 @@ +#include "Graphics.h" +#include "../Platform/Window.h" +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + +namespace Artifact { + +void Graphics::init() { + instance = wgpuCreateInstance(nullptr); + + window = client->subsystem(); + + surface = window->createWGPUSurface(instance); + + WGPURequestAdapterOptions opts { + .compatibleSurface = surface + }; + wgpuInstanceRequestAdapter(instance, &opts, { + .mode = WGPUCallbackMode_AllowProcessEvents, + .callback = [](auto status, auto adapter, auto msg, auto userdata, auto) { + if (status == WGPURequestAdapterStatus_Success) { + static_cast(userdata)->adapter = adapter; + } else { + throw std::runtime_error("Failed to get wgpu adapter."); + } + }, + .userdata1 = this + }); + + // To avoid dealing with concurrency, we just block until the adapter is available (or the program errors). + while (!adapter) wgpuInstanceProcessEvents(instance); + + WGPUDeviceDescriptor desc { + .uncapturedErrorCallbackInfo = { + .callback = [](auto device, auto type, auto msg, auto, auto) { + const char * message = (msg.data ? reinterpret_cast(msg.data) : "No message"); + fprintf(stderr, "Uncaptured WebGPU Error (type %d): %s\n", type, message); + }} + }; + wgpuAdapterRequestDevice(adapter, &desc, { + .mode = WGPUCallbackMode_AllowProcessEvents, + .callback = [](auto status, auto device, auto msg, auto userdata, auto) { + if (status == WGPURequestDeviceStatus_Success) { + static_cast(userdata)->device = device; + } else { + throw std::runtime_error("Failed to get wgpu device."); + } + }, + .userdata1 = this + }); + + while (!device) wgpuInstanceProcessEvents(instance); + + WGPUTextureDescriptor depthDesc = {}; + depthDesc.dimension = WGPUTextureDimension_2D; + depthDesc.size.width = 1080; + depthDesc.size.height = 640; + depthDesc.size.depthOrArrayLayers = 1; + depthDesc.format = WGPUTextureFormat_Depth32Float; + depthDesc.usage = WGPUTextureUsage_RenderAttachment; + depthDesc.mipLevelCount = 1; + depthDesc.sampleCount = 1; + depthTexture = wgpuDeviceCreateTexture(device, &depthDesc); + depthTextureView = wgpuTextureCreateView(depthTexture, nullptr); + + queue = wgpuDeviceGetQueue(device); + + WGPUSurfaceConfiguration surfaceConfig = {}; + surfaceConfig.device = device; + surfaceConfig.format = WGPUTextureFormat_BGRA8Unorm; // FIXME: Auto-detect + surfaceConfig.usage = WGPUTextureUsage_RenderAttachment; + surfaceConfig.width = 1080; + surfaceConfig.height = 640; + surfaceConfig.presentMode = WGPUPresentMode_Fifo; + wgpuSurfaceConfigure(surface, &surfaceConfig); + + for (auto & system : subsystems) { + system->graphics = this; + printf("Graphics: %p", system->graphics); + system->init(); + system->reload(); + } +} + +void Graphics::deinit() { + for (auto & system : subsystems) { + system->deinit(); + } + + wgpuTextureDestroy(depthTexture); + wgpuQueueRelease(queue); + wgpuDeviceDestroy(device); + wgpuAdapterRelease(adapter); + wgpuSurfaceRelease(surface); + wgpuInstanceRelease(instance); +} + +void Graphics::render() { + WGPUSurfaceTexture surfaceTexture; + wgpuSurfaceGetCurrentTexture(surface, &surfaceTexture); + if (surfaceTexture.status == WGPUSurfaceGetCurrentTextureStatus_Error || !surfaceTexture.texture) { + throw std::runtime_error("Failed to get surface texture"); + } + WGPUTextureView nextTexture = wgpuTextureCreateView(surfaceTexture.texture, nullptr); + if (!nextTexture) { + throw std::runtime_error("Failed to create texture view"); + } + + WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(device, nullptr); + + for (auto & system : subsystems) { + system->render(nextTexture, encoder); + } + + WGPUCommandBufferDescriptor cmdBufferDesc = {}; + cmdBufferDesc.label = WGPUStringView{"Command buffer", WGPU_STRLEN}; + WGPUCommandBuffer cmdBuffer = wgpuCommandEncoderFinish(encoder, nullptr); + wgpuQueueSubmit(queue, 1, &cmdBuffer); +#ifndef WASM_BUILD + wgpuSurfacePresent(surface); +#endif + wgpuTextureViewRelease(nextTexture); + wgpuTextureRelease(surfaceTexture.texture); +} + +WGPUTexture Graphics::createTextureFromData(const void* data, uint32_t width, uint32_t height) { + WGPUTextureDescriptor textureDesc = {}; + textureDesc.dimension = WGPUTextureDimension_2D; + textureDesc.size.width = width; + textureDesc.size.height = height; + textureDesc.size.depthOrArrayLayers = 1; + textureDesc.format = WGPUTextureFormat_RGBA8Unorm; + textureDesc.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst; + textureDesc.mipLevelCount = 1; + textureDesc.sampleCount = 1; + auto texture = wgpuDeviceCreateTexture(device, &textureDesc); + +// WGPUTextureViewDescriptor viewDesc = {}; +// viewDesc.format = WGPUTextureFormat_RGBA8Unorm; +// viewDesc.dimension = WGPUTextureViewDimension_2D; +// viewDesc.mipLevelCount = 1; +// viewDesc.arrayLayerCount = 1; +// tex->view = wgpuTextureCreateView(tex->texture, &viewDesc); + + WGPUTexelCopyTextureInfo destination = {}; + destination.texture = texture; + destination.mipLevel = 0; + destination.origin = {0, 0, 0}; + destination.aspect = WGPUTextureAspect_All; + WGPUTexelCopyBufferLayout layout = {}; + layout.offset = 0; + layout.bytesPerRow = width * 4; + layout.rowsPerImage = height; + WGPUExtent3D writeSize = {width, height, 1}; + wgpuQueueWriteTexture(queue, &destination, data, width * height * 4, &layout, &writeSize); + + return texture; +} + +WGPUTexture Graphics::createTextureFromFile(const char* file) { + int width, height, channels; + auto data = stbi_load(file, &width, &height, &channels, 4); + auto out = createTextureFromData(data, width, height); + stbi_image_free(data); + return out; +} + +} diff --git a/Client/Graphics/Graphics.h b/Client/Graphics/Graphics.h new file mode 100644 index 0000000..aa22c30 --- /dev/null +++ b/Client/Graphics/Graphics.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include +#include "../Client.h" +#include "../Platform/Window.h" + +namespace Artifact { + +class Graphics; + +class GraphicsSubsystem { +public: + Graphics * graphics; + virtual void init() {} + virtual void reload() {} + virtual void deinit() {} + virtual void render(WGPUTextureView nextTexture, WGPUCommandEncoder encoder) {} +}; + +class Graphics: public ClientSubsystem, public Engine { +public: + WGPUInstance instance = nullptr; + WGPUSurface surface = nullptr; + WGPUAdapter adapter = nullptr; + WGPUDevice device = nullptr; + + WGPUTexture depthTexture = nullptr; + WGPUTextureView depthTextureView = nullptr; + WGPUQueue queue = nullptr; + + WindowImpl * window; + + void init() override; + void deinit() override; + void render() override; + + WGPUTexture createTextureFromData(const void* data, uint32_t width, uint32_t height); + WGPUTexture createTextureFromFile(const char* file); +}; + +} diff --git a/Client/Graphics/UIRenderer.cpp b/Client/Graphics/UIRenderer.cpp new file mode 100644 index 0000000..adae09e --- /dev/null +++ b/Client/Graphics/UIRenderer.cpp @@ -0,0 +1,429 @@ +#include "UIRenderer.h" + +#include + +#define NK_IMPLEMENTATION +#include +#include + +namespace Artifact { + +namespace Events { + +struct DrawUI {}; + +} + +static std::array(Key::Last) + 1> ArtifactToNuklear {}; + +namespace { + +static struct _nkInit { + _nkInit() { + // Populate the Artifact-to-Nuklear key map. +#define X(nuklear, artifact) ArtifactToNuklear[static_cast(Key::artifact)] = nuklear + X(NK_KEY_CTRL, ControlLeft); + X(NK_KEY_CTRL, ControlRight); + + X(NK_KEY_SHIFT, ShiftLeft); + X(NK_KEY_SHIFT, ShiftRight); + + X(NK_KEY_ENTER, Enter); + + X(NK_KEY_BACKSPACE, DeleteBackward); + X(NK_KEY_DEL, DeleteForward); + + X(NK_KEY_LEFT, ArrowLeft); + X(NK_KEY_RIGHT, ArrowRight); + X(NK_KEY_UP, ArrowUp); + X(NK_KEY_DOWN, ArrowDown); + + X(NK_KEY_TAB, Tab); +#undef X + } +} __nkInit; + +} + +void UIRenderer::init() { + printf("UI: %p", graphics); + + graphics->window->listen([this](auto ev) { + nk_input_begin(&ctx); + }); + + graphics->window->listen([this](auto ev) { + nk_input_end(&ctx); + }); + + graphics->window->listen([this](auto ev) { + nk_input_motion(&ctx, ev.x, ev.y); + }); + + graphics->window->listen([this](auto ev) { + nk_input_scroll(&ctx, nk_vec2(ev.dx, ev.dy)); + }); + + graphics->window->listen([this](auto ev) { + nk_input_button(&ctx, ev.button == Events::MOUSE_BUTTON_RIGHT ? NK_BUTTON_RIGHT : NK_BUTTON_LEFT, (int)ev.x, (int)ev.y, ev.state); + }); + + graphics->window->listen([this](auto ev) { + nk_input_key(&ctx, ArtifactToNuklear[static_cast(ev.key)], true); + }); + + graphics->window->listen([this](auto ev) { + nk_input_key(&ctx, ArtifactToNuklear[static_cast(ev.key)], false); + }); + + graphics->window->listen([this](auto ev) { + nk_input_char(&ctx, ev.codepoint); + }); + + nk_init_default(&ctx, nullptr); + + nk_font_atlas_init_default(&atlas); + nk_font_atlas_begin(&atlas); + + nk_font* font; + void* fontData = nullptr; + + // FIXME: Automatic path resolution + std::string assetPath = std::string(getenv("HOME")) + "/eclipse-workspace/ArtifactEngine/assets"; + + FILE* fontFile = fopen((assetPath + "/fonts/Arial.ttf").c_str(), "rb"); + if (fontFile) { + fseek(fontFile, 0, SEEK_END); + long fontSize = ftell(fontFile); + fseek(fontFile, 0, SEEK_SET); + fontData = malloc(fontSize); + fread(fontData, 1, fontSize, fontFile); + fclose(fontFile); + font = nk_font_atlas_add_from_memory(&atlas, fontData, fontSize, 13.0f, nullptr); + } else { + font = nk_font_atlas_add_default(&atlas, 13.0f, nullptr); + } + + int fontWidth, fontHeight; + const void* fontImage = nk_font_atlas_bake(&atlas, &fontWidth, &fontHeight, NK_FONT_ATLAS_RGBA32); + fontTexture = graphics->createTextureFromData(fontImage, fontWidth, fontHeight); + fontTextureView = wgpuTextureCreateView(fontTexture, nullptr); + nk_font_atlas_end(&atlas, nk_handle_id(0), nullptr); + nk_style_set_font(&ctx, &font->handle); + if (fontData) free(fontData); + + unsigned char whitePixel[4] = {255, 255, 255, 255}; + dummyTexture = graphics->createTextureFromData(whitePixel, 1, 1); + dummyTextureView = wgpuTextureCreateView(dummyTexture, nullptr); + + nk_buffer_init_default(&vertexBufferNK); + nk_buffer_init_default(&indexBufferNK); + nk_buffer_init_default(&commandBufferNK); +} + +struct NKVertex { + float pos[2]; + float uv[2]; + uint8_t color[4]; +}; + +void UIRenderer::reload() { + WGPUBufferDescriptor uiVertexBufferDesc = {}; + uiVertexBufferDesc.size = 1024 * 1024 * sizeof(NKVertex); + uiVertexBufferDesc.usage = WGPUBufferUsage_Vertex | WGPUBufferUsage_CopyDst; + uiVertexBufferDesc.label = WGPUStringView{"UI Vertex Buffer", WGPU_STRLEN}; + vertexBuffer = wgpuDeviceCreateBuffer(graphics->device, &uiVertexBufferDesc); + + WGPUBufferDescriptor uiIndexBufferDesc = {}; + uiIndexBufferDesc.size = 1024 * 1024 * sizeof(uint16_t); + uiIndexBufferDesc.usage = WGPUBufferUsage_Index | WGPUBufferUsage_CopyDst; + uiIndexBufferDesc.label = WGPUStringView{"UI Index Buffer", WGPU_STRLEN}; + indexBuffer = wgpuDeviceCreateBuffer(graphics->device, &uiIndexBufferDesc); + + WGPUBufferDescriptor uiUniformBufferDesc = {}; + uiUniformBufferDesc.size = sizeof(glm::mat4); + uiUniformBufferDesc.usage = WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst; + uiUniformBufferDesc.label = WGPUStringView{"UI Uniform Buffer", WGPU_STRLEN}; + uniformBuffer = wgpuDeviceCreateBuffer(graphics->device, &uiUniformBufferDesc); + + std::string uiVSCode = R"( + struct Uniforms { + ortho: mat4x4, + } + @group(0) @binding(0) var uniforms: Uniforms; + struct VertexOutput { + @builtin(position) pos: vec4, + @location(0) uv: vec2, + @location(1) color: vec4, + } + @vertex + fn vs_main(@location(0) pos: vec2, @location(1) uv: vec2, @location(2) color: vec4) -> VertexOutput { + var out: VertexOutput; + out.pos = uniforms.ortho * vec4(pos, 0.0, 1.0); + out.uv = uv; + out.color = color; + return out; + } + )"; + std::string uiFSCode = R"( + @group(0) @binding(1) var texture: texture_2d; + @group(0) @binding(2) var sampler_: sampler; + @fragment + fn fs_main(@location(0) uv: vec2, @location(1) color: vec4) -> @location(0) vec4 { + return textureSample(texture, sampler_, uv) * color; + } + )"; + + WGPUShaderModuleDescriptor uiVSDesc = {}; + WGPUShaderSourceWGSL src = {.code = WGPUStringView{uiVSCode.c_str(), uiVSCode.size()}}; + src.chain.sType = WGPUSType_ShaderSourceWGSL; + uiVSDesc.nextInChain = &src.chain; + WGPUShaderModule uiVSModule = wgpuDeviceCreateShaderModule(graphics->device, &uiVSDesc); + + WGPUShaderModuleDescriptor uiFSDesc = {}; + src = {.code = WGPUStringView{uiFSCode.c_str(), uiFSCode.size()}}; + src.chain.sType = WGPUSType_ShaderSourceWGSL; + uiFSDesc.nextInChain = &src.chain; + WGPUShaderModule uiFSModule = wgpuDeviceCreateShaderModule(graphics->device, &uiFSDesc); + + WGPUSamplerDescriptor uiSamplerDesc = {}; + uiSamplerDesc.addressModeU = WGPUAddressMode_ClampToEdge; + uiSamplerDesc.addressModeV = WGPUAddressMode_ClampToEdge; + uiSamplerDesc.addressModeW = WGPUAddressMode_ClampToEdge; + uiSamplerDesc.magFilter = WGPUFilterMode_Linear; + uiSamplerDesc.minFilter = WGPUFilterMode_Linear; + uiSamplerDesc.mipmapFilter = WGPUMipmapFilterMode_Linear; + uiSamplerDesc.maxAnisotropy = 1; + sampler = wgpuDeviceCreateSampler(graphics->device, &uiSamplerDesc); + if (!sampler) { + throw std::runtime_error("Failed to create UI sampler"); + } + + WGPUBindGroupLayoutEntry uiBglEntries[3] = {}; + uiBglEntries[0].binding = 0; + uiBglEntries[0].visibility = WGPUShaderStage_Vertex; + uiBglEntries[0].buffer.type = WGPUBufferBindingType_Uniform; + uiBglEntries[1].binding = 1; + uiBglEntries[1].visibility = WGPUShaderStage_Fragment; + uiBglEntries[1].texture.sampleType = WGPUTextureSampleType_Float; + uiBglEntries[1].texture.viewDimension = WGPUTextureViewDimension_2D; + uiBglEntries[2].binding = 2; + uiBglEntries[2].visibility = WGPUShaderStage_Fragment; + uiBglEntries[2].sampler.type = WGPUSamplerBindingType_Filtering; + + WGPUBindGroupLayoutDescriptor uiBglDesc = {}; + uiBglDesc.entryCount = 3; + uiBglDesc.entries = uiBglEntries; + bgl = wgpuDeviceCreateBindGroupLayout(graphics->device, &uiBglDesc); + if (!bgl) { + throw std::runtime_error("Failed to create bind group layout"); + } + + WGPUVertexAttribute uiAttributes[3] = {}; + uiAttributes[0].format = WGPUVertexFormat_Float32x2; + uiAttributes[0].offset = offsetof(NKVertex, pos); + uiAttributes[0].shaderLocation = 0; + uiAttributes[1].format = WGPUVertexFormat_Float32x2; + uiAttributes[1].offset = offsetof(NKVertex, uv); + uiAttributes[1].shaderLocation = 1; + uiAttributes[2].format = WGPUVertexFormat_Unorm8x4; + uiAttributes[2].offset = offsetof(NKVertex, color); + uiAttributes[2].shaderLocation = 2; + + WGPUVertexBufferLayout uiVBLayout = {}; + uiVBLayout.arrayStride = sizeof(NKVertex); + uiVBLayout.stepMode = WGPUVertexStepMode_Vertex; + uiVBLayout.attributeCount = 3; + uiVBLayout.attributes = uiAttributes; + + WGPUBlendState uiBlendState = {}; + uiBlendState.color.srcFactor = WGPUBlendFactor_SrcAlpha; + uiBlendState.color.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha; + uiBlendState.color.operation = WGPUBlendOperation_Add; + uiBlendState.alpha.srcFactor = WGPUBlendFactor_One; + uiBlendState.alpha.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha; + uiBlendState.alpha.operation = WGPUBlendOperation_Add; + + WGPUColorTargetState uiColorTarget = {}; + uiColorTarget.format = WGPUTextureFormat_BGRA8Unorm; + uiColorTarget.blend = &uiBlendState; + uiColorTarget.writeMask = WGPUColorWriteMask_All; + + WGPUFragmentState uiFragmentState = {}; + uiFragmentState.module = uiFSModule; + uiFragmentState.entryPoint = WGPUStringView{"fs_main", WGPU_STRLEN}; + uiFragmentState.targetCount = 1; + uiFragmentState.targets = &uiColorTarget; + + WGPUPipelineLayoutDescriptor uiPlDesc = {}; + uiPlDesc.bindGroupLayoutCount = 1; + uiPlDesc.bindGroupLayouts = &bgl; + WGPUPipelineLayout uiPipelineLayout = wgpuDeviceCreatePipelineLayout(graphics->device, &uiPlDesc); + + WGPURenderPipelineDescriptor uiPipelineDesc = {}; + uiPipelineDesc.vertex.module = uiVSModule; + uiPipelineDesc.vertex.entryPoint = WGPUStringView{"vs_main", WGPU_STRLEN}; + uiPipelineDesc.vertex.bufferCount = 1; + uiPipelineDesc.vertex.buffers = &uiVBLayout; + uiPipelineDesc.fragment = &uiFragmentState; + uiPipelineDesc.primitive.topology = WGPUPrimitiveTopology_TriangleList; + uiPipelineDesc.primitive.frontFace = WGPUFrontFace_CCW; + uiPipelineDesc.primitive.cullMode = WGPUCullMode_None; + uiPipelineDesc.layout = uiPipelineLayout; + uiPipelineDesc.multisample.count = 1; + uiPipelineDesc.multisample.mask = ~0u; + + pipeline = wgpuDeviceCreateRenderPipeline(graphics->device, &uiPipelineDesc); +} + +std::string txt = "Test"; +int _txt = 0; + +void UIRenderer::render(WGPUTextureView nextTexture, WGPUCommandEncoder encoder) { + if (nk_begin(&ctx, "Test", nk_rect(10, 10, 100, 100), NK_WINDOW_TITLE | NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_SCALABLE)) { + nk_layout_row_dynamic(&ctx, 50, 2); + nk_label(&ctx, "Test", 0); + nk_button_label(&ctx, "Test"); + + nk_layout_row_dynamic(&ctx, 50, 2); + nk_label(&ctx, "Test", 0); + nk_button_label(&ctx, "Test"); + + nk_layout_row_dynamic(&ctx, 50, 1); + nk_edit_string(&ctx, NK_EDIT_BOX, txt.data(), &_txt, 100, nk_filter_ascii); + + if (nk_contextual_begin(&ctx, NK_PANEL_CONTEXTUAL, nk_vec2(100, 100), nk_rect(10, 10, 100, 100))) { + nk_layout_row_dynamic(&ctx, 25, 1); + nk_contextual_item_label(&ctx, "Button", NK_TEXT_CENTERED); + nk_contextual_end(&ctx); + } + } + + nk_end(&ctx); + + dispatch(Events::DrawUI {}); + + nk_buffer_clear(&vertexBufferNK); + nk_buffer_clear(&indexBufferNK); + nk_buffer_clear(&commandBufferNK); + struct nk_convert_config config = {}; + static const struct nk_draw_vertex_layout_element vertex_layout[] = { + {NK_VERTEX_POSITION, NK_FORMAT_FLOAT, NK_OFFSETOF(NKVertex, pos)}, + {NK_VERTEX_TEXCOORD, NK_FORMAT_FLOAT, NK_OFFSETOF(NKVertex, uv)}, + {NK_VERTEX_COLOR, NK_FORMAT_R8G8B8A8, NK_OFFSETOF(NKVertex, color)}, + {NK_VERTEX_LAYOUT_END} + }; + config.vertex_layout = vertex_layout; + config.vertex_size = sizeof(NKVertex); + config.vertex_alignment = alignof(NKVertex); + config.tex_null.texture = nk_handle_id(0); + config.tex_null.uv = {0.0f, 0.0f}; + config.circle_segment_count = 22; + config.curve_segment_count = 22; + config.arc_segment_count = 22; + config.global_alpha = 1.0f; + config.shape_AA = NK_ANTI_ALIASING_ON; + config.line_AA = NK_ANTI_ALIASING_ON; + nk_convert(&ctx, &commandBufferNK, &vertexBufferNK, &indexBufferNK, &config); + + // FIXME: Variable size + int viewportWidth = 1080; + int viewportHeight = 640; + + auto ortho = glm::ortho(0.0f, (float)viewportWidth, (float)viewportHeight, 0.0f, -1.0f, 1.0f); + wgpuQueueWriteBuffer(graphics->queue, uniformBuffer, 0, &ortho, sizeof(ortho)); + + WGPURenderPassColorAttachment colorAttachment = {}; + colorAttachment.view = nextTexture; + colorAttachment.loadOp = WGPULoadOp_Load; + colorAttachment.storeOp = WGPUStoreOp_Store; + colorAttachment.clearValue = {0, 0.2, 0.4, 1}; + colorAttachment.depthSlice = -1; + + WGPURenderPassDepthStencilAttachment depthAttachment = {}; + depthAttachment.view = graphics->depthTextureView; + depthAttachment.depthLoadOp = WGPULoadOp_Clear; + depthAttachment.depthStoreOp = WGPUStoreOp_Store; + depthAttachment.depthClearValue = 1.0f; + depthAttachment.depthReadOnly = false; + depthAttachment.stencilLoadOp = WGPULoadOp_Undefined; + depthAttachment.stencilStoreOp = WGPUStoreOp_Undefined; + + WGPURenderPassDescriptor renderPassDesc = {}; + renderPassDesc.colorAttachmentCount = 1; + renderPassDesc.colorAttachments = &colorAttachment; + renderPassDesc.depthStencilAttachment = &depthAttachment; + + colorAttachment.loadOp = WGPULoadOp_Load; + WGPURenderPassDescriptor uiRenderPassDesc = {}; + uiRenderPassDesc.colorAttachmentCount = 1; + uiRenderPassDesc.colorAttachments = &colorAttachment; + + WGPURenderPassEncoder uiRenderPass = wgpuCommandEncoderBeginRenderPass(encoder, &uiRenderPassDesc); + wgpuRenderPassEncoderSetPipeline(uiRenderPass, pipeline); + wgpuRenderPassEncoderSetViewport(uiRenderPass, 0, 0, (float)viewportWidth, (float)viewportHeight, 0.0f, 1.0f); + + + size_t vertexSize = nk_buffer_total(&vertexBufferNK); + size_t indexSize = nk_buffer_total(&indexBufferNK); + if (vertexSize > 0 && indexSize > 0) { + wgpuQueueWriteBuffer(graphics->queue, vertexBuffer, 0, vertexBufferNK.memory.ptr, vertexSize); + wgpuQueueWriteBuffer(graphics->queue, indexBuffer, 0, indexBufferNK.memory.ptr, indexSize); + + wgpuRenderPassEncoderSetVertexBuffer(uiRenderPass, 0, vertexBuffer, 0, vertexSize); + wgpuRenderPassEncoderSetIndexBuffer(uiRenderPass, indexBuffer, WGPUIndexFormat_Uint16, 0, indexSize); + + std::vector bindGroups; + WGPUBindGroupEntry uiBgEntries[3] = {}; + uiBgEntries[0].binding = 0; + uiBgEntries[0].buffer = uniformBuffer; + uiBgEntries[0].offset = 0; + uiBgEntries[0].size = sizeof(glm::mat4); + uiBgEntries[2].binding = 2; + uiBgEntries[2].sampler = sampler; + + std::unordered_map textureMap = {{0, fontTextureView}}; + const struct nk_draw_command* cmd; + uint32_t offset = 0; + nk_draw_foreach(cmd, &ctx, &commandBufferNK) { + if (!cmd->elem_count) continue; + if (offset + cmd->elem_count > indexSize / sizeof(uint16_t)) break; + + uint32_t scissorX = static_cast(cmd->clip_rect.x < 0 ? 0 : cmd->clip_rect.x); + uint32_t scissorY = static_cast(cmd->clip_rect.y < 0 ? 0 : cmd->clip_rect.y); + uint32_t scissorW = static_cast(cmd->clip_rect.w); + uint32_t scissorH = static_cast(cmd->clip_rect.h); + scissorX = std::min(scissorX, (uint32_t)viewportWidth - 1); + scissorY = std::min(scissorY, (uint32_t)viewportHeight - 1); + if (scissorW > viewportWidth || scissorH > viewportHeight || scissorX + scissorW > viewportWidth || scissorY + scissorH > viewportHeight) { + scissorW = std::min(scissorW, viewportWidth - scissorX); + scissorH = std::min(scissorH, viewportHeight - scissorY); + } + wgpuRenderPassEncoderSetScissorRect(uiRenderPass, scissorX, scissorY, scissorW, scissorH); + + uiBgEntries[1].binding = 1; + uiBgEntries[1].textureView = cmd->texture.id >= 0 && textureMap.count(cmd->texture.id) ? textureMap[cmd->texture.id] : dummyTextureView; + + WGPUBindGroupDescriptor uiBgDesc = {}; + uiBgDesc.layout = bgl; + uiBgDesc.entryCount = 3; + uiBgDesc.entries = uiBgEntries; + + WGPUBindGroup uiDynamicBindGroup = wgpuDeviceCreateBindGroup(graphics->device, &uiBgDesc); + bindGroups.push_back(uiDynamicBindGroup); + + wgpuRenderPassEncoderSetBindGroup(uiRenderPass, 0, uiDynamicBindGroup, 0, nullptr); + wgpuRenderPassEncoderDrawIndexed(uiRenderPass, cmd->elem_count, 1, offset, 0, 0); + + offset += cmd->elem_count; + } + wgpuRenderPassEncoderEnd(uiRenderPass); + for (WGPUBindGroup bg : bindGroups) wgpuBindGroupRelease(bg); + } else { + wgpuRenderPassEncoderEnd(uiRenderPass); + } + + nk_clear(&ctx); +} + +} diff --git a/Client/Graphics/UIRenderer.h b/Client/Graphics/UIRenderer.h new file mode 100644 index 0000000..7c32f29 --- /dev/null +++ b/Client/Graphics/UIRenderer.h @@ -0,0 +1,46 @@ +#pragma once + +#define NK_INCLUDE_FIXED_TYPES +#define NK_INCLUDE_STANDARD_IO +#define NK_INCLUDE_STANDARD_VARARGS +#define NK_INCLUDE_DEFAULT_ALLOCATOR +#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT +#define NK_INCLUDE_FONT_BAKING +#define NK_INCLUDE_DEFAULT_FONT +#include +#include + +#include +#include "Graphics.h" +#include "../Platform/Window.h" + +namespace Artifact { + +class UIRenderer: public GraphicsSubsystem, public EventTarget { + nk_context ctx {}; + nk_font_atlas atlas {}; + + WGPURenderPipeline pipeline = nullptr; + + WGPUBuffer vertexBuffer = nullptr; + WGPUBuffer indexBuffer = nullptr; + WGPUBuffer uniformBuffer = nullptr; + WGPUSampler sampler = nullptr; + WGPUBindGroupLayout bgl = nullptr; + WGPUTexture fontTexture = nullptr; + WGPUTextureView fontTextureView = nullptr; + WGPUTexture dummyTexture = nullptr; + WGPUTextureView dummyTextureView = nullptr; + + nk_buffer vertexBufferNK; + nk_buffer indexBufferNK; + nk_buffer commandBufferNK; + + void init() override; + void reload() override; + void render(WGPUTextureView nextTexture, WGPUCommandEncoder encoder) override; + + WGPURenderPipeline makePipeline(); +}; + +} diff --git a/Client/Graphics/WorldRenderer.cpp b/Client/Graphics/WorldRenderer.cpp new file mode 100644 index 0000000..8d3a057 --- /dev/null +++ b/Client/Graphics/WorldRenderer.cpp @@ -0,0 +1 @@ +#include "WorldRenderer.h" diff --git a/Client/Graphics/WorldRenderer.h b/Client/Graphics/WorldRenderer.h new file mode 100644 index 0000000..56c16d3 --- /dev/null +++ b/Client/Graphics/WorldRenderer.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace Artifact { + +class WorldRenderer { + +}; + +} diff --git a/Client/LocalPlayer.cpp b/Client/LocalPlayer.cpp new file mode 100644 index 0000000..3fbaba6 --- /dev/null +++ b/Client/LocalPlayer.cpp @@ -0,0 +1,17 @@ +#include "LocalPlayer.h" + +#include "Platform/Window.h" + +namespace Artifact { + +void LocalPlayer::init() { + window = client->subsystem(); +} + +void LocalPlayer::tick() { + if (window->isKeyDown(Key::W)) { + + } +} + +} diff --git a/Client/LocalPlayer.h b/Client/LocalPlayer.h new file mode 100644 index 0000000..395ac16 --- /dev/null +++ b/Client/LocalPlayer.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include "Client.h" +#include "Graphics/Camera.h" + +namespace Artifact { + +class LocalPlayerImpl: public ClientSubsystem { +public: + std::unique_ptr camera = nullptr; +}; + +class LocalPlayer: public LocalPlayerImpl { + WindowImpl * window = nullptr; + +public: + LocalPlayer() { + //camera = std::make_unique(glm::vec3(0, 0, 0), 75); + } + + void init() override; + void tick() override; +}; + +} diff --git a/Client/Network.h b/Client/Network.h new file mode 100644 index 0000000..4340e6e --- /dev/null +++ b/Client/Network.h @@ -0,0 +1,11 @@ +#pragma once + +#include "Client.h" +#include + +namespace Artifact { + +class ClientNetwork: public NetworkClient, public ClientSubsystem {}; + +} + diff --git a/Client/Platform/Keybinds.cpp b/Client/Platform/Keybinds.cpp new file mode 100644 index 0000000..9a84be9 --- /dev/null +++ b/Client/Platform/Keybinds.cpp @@ -0,0 +1,12 @@ +#include "Keybinds.h" + +namespace Artifact { + +namespace Keybinds { + + + +} + +} + diff --git a/Client/Platform/Keybinds.h b/Client/Platform/Keybinds.h new file mode 100644 index 0000000..989db40 --- /dev/null +++ b/Client/Platform/Keybinds.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Window.h" + +namespace Artifact { + +namespace Keybinds { + + + +} + +} diff --git a/Client/Platform/Window.cpp b/Client/Platform/Window.cpp new file mode 100644 index 0000000..e482e50 --- /dev/null +++ b/Client/Platform/Window.cpp @@ -0,0 +1,177 @@ +#include + +#include "Window.h" +#include "glfw3webgpu/glfw3webgpu.h" + +namespace Artifact { + +static std::array GLFWToArtifact {}; +static std::array(Key::Last) + 1> ArtifactToGLFW {}; + +namespace { +// Ensure that GLFW is transparently initialized prior to window creation. +static struct _glfwInit { + _glfwInit() { + if(!glfwInit()) throw std::runtime_error("Failed to initialize GLFW."); + + // Populate the global GLFW-to-Artifact keycode mapping. + GLFWToArtifact.fill(Key::Unknown); +#define X(glfw, artifact) GLFWToArtifact[glfw] = Key::artifact;\ + ArtifactToGLFW[static_cast(Key::artifact)] = glfw + X(GLFW_KEY_A, A); + X(GLFW_KEY_B, B); + X(GLFW_KEY_C, C); + X(GLFW_KEY_D, D); + X(GLFW_KEY_E, E); + X(GLFW_KEY_F, F); + X(GLFW_KEY_G, G); + X(GLFW_KEY_H, H); + X(GLFW_KEY_I, I); + X(GLFW_KEY_J, J); + X(GLFW_KEY_K, K); + X(GLFW_KEY_L, L); + X(GLFW_KEY_M, M); + X(GLFW_KEY_N, N); + X(GLFW_KEY_O, O); + X(GLFW_KEY_P, P); + X(GLFW_KEY_Q, Q); + X(GLFW_KEY_R, R); + X(GLFW_KEY_S, S); + X(GLFW_KEY_T, T); + X(GLFW_KEY_U, U); + X(GLFW_KEY_V, V); + X(GLFW_KEY_W, W); + X(GLFW_KEY_X, X); + X(GLFW_KEY_Y, Y); + X(GLFW_KEY_Z, Z); + + X(GLFW_KEY_0, Zero); + X(GLFW_KEY_1, One); + X(GLFW_KEY_2, Two); + X(GLFW_KEY_3, Three); + X(GLFW_KEY_4, Four); + X(GLFW_KEY_5, Five); + X(GLFW_KEY_6, Six); + X(GLFW_KEY_7, Seven); + X(GLFW_KEY_8, Eight); + X(GLFW_KEY_9, Nine); + + X(GLFW_KEY_ENTER, Enter); + + X(GLFW_KEY_LEFT_SHIFT, ShiftLeft); + X(GLFW_KEY_RIGHT_SHIFT, ShiftRight); + + X(GLFW_KEY_LEFT_CONTROL, ControlLeft); + X(GLFW_KEY_RIGHT_CONTROL, ControlRight); + + X(GLFW_KEY_LEFT_ALT, OptionLeft); + X(GLFW_KEY_RIGHT_ALT, OptionRight); + + X(GLFW_KEY_LEFT_SUPER, CommandLeft); + X(GLFW_KEY_RIGHT_SUPER, CommandRight); + + X(GLFW_KEY_BACKSPACE, DeleteBackward); + X(GLFW_KEY_DELETE, DeleteForward); + + X(GLFW_KEY_LEFT, ArrowLeft); + X(GLFW_KEY_RIGHT, ArrowRight); + X(GLFW_KEY_UP, ArrowUp); + X(GLFW_KEY_DOWN, ArrowDown); + + X(GLFW_KEY_TAB, Tab); +#undef X + } + + ~_glfwInit() { + glfwTerminate(); + } +} __glfwInit; +} + +Window::Window(uint32_t width, uint32_t height, std::string title) { + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + window = glfwCreateWindow(width, height, "", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + + glfwSetCursorPosCallback(window, [](auto window, auto x, auto y) { + static_cast(glfwGetWindowUserPointer(window))->dispatch(Events::CursorPosEvent { + .x = x, + .y = y + }); + }); + + glfwSetMouseButtonCallback(window, [](auto window, auto button, auto action, auto mods) { + Events::MouseButton myButton = Events::MOUSE_BUTTON_LEFT; + if (button == GLFW_MOUSE_BUTTON_2) { + myButton = Events::MOUSE_BUTTON_RIGHT; + } else if (button == GLFW_MOUSE_BUTTON_3) { + myButton = Events::MOUSE_BUTTON_MIDDLE; + } + + double x, y; + glfwGetCursorPos(window, &x, &y); + static_cast(glfwGetWindowUserPointer(window))->dispatch(Events::MouseEvent { + .button = myButton, + .state = action == GLFW_PRESS, + .x = x, + .y = y + }); + }); + + glfwSetScrollCallback(window, [](auto window, auto dx, auto dy) { + static_cast(glfwGetWindowUserPointer(window))->dispatch(Events::ScrollEvent { + .dx = dx, + .dy = dy + }); + }); + + glfwSetKeyCallback(window, [](auto window, auto key, auto scancode, auto action, auto mods) { + if (action == GLFW_PRESS) { + static_cast(glfwGetWindowUserPointer(window))->dispatch(Events::KeyDownEvent { + .key = GLFWToArtifact[key] + }); + } else if (action == GLFW_RELEASE) { + static_cast(glfwGetWindowUserPointer(window))->dispatch(Events::KeyUpEvent { + .key = GLFWToArtifact[key] + }); + } + }); + + glfwSetCharCallback(window, [](auto window, auto codepoint) { + static_cast(glfwGetWindowUserPointer(window))->dispatch(Events::CharInputEvent { + .codepoint = codepoint + }); + }); +} + +void Window::render() { + dispatch(Events::InputBegin {}); + glfwPollEvents(); + dispatch(Events::InputEnd {}); +} + +WGPUSurface Window::createWGPUSurface(WGPUInstance instance) { + return glfwCreateWindowWGPUSurface(instance, window); +} + +void Window::setTitle(std::string title) { + glfwSetWindowTitle(window, title.c_str()); +} + +bool Window::shouldClose() { + return glfwWindowShouldClose(window); +} + +void Window::setPointerLock() { + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); +} + +void Window::releasePointerLock() { + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); +} + +bool Window::isKeyDown(Key key) { + return glfwGetKey(window, ArtifactToGLFW[static_cast(key)]) == GLFW_PRESS; +} + +} diff --git a/Client/Platform/Window.h b/Client/Platform/Window.h new file mode 100644 index 0000000..7349acf --- /dev/null +++ b/Client/Platform/Window.h @@ -0,0 +1,103 @@ +#pragma once +#include + +#include +#include + +#include +#include "Events.h" +#include "Client.h" + +namespace Artifact { + +enum class Key { + Unknown = 0, + A, B, C, D, E, F, G, H, I, J, K, L, M, + N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + Zero, One, Two, Three, Four, Five, + Six, Seven, Eight, Nine, + ShiftLeft, ShiftRight, + ControlLeft, ControlRight, + OptionLeft, OptionRight, + CommandLeft, CommandRight, + Enter, + DeleteForward, DeleteBackward, + ArrowLeft, ArrowRight, ArrowUp, ArrowDown, + Tab, + + Last +}; + +namespace Events { + +struct InputBegin {}; +struct InputEnd {}; + +enum Action { + ACTION_PRESS, + ACTION_RELEASE +}; + +struct KeyDownEvent { + Key key; +}; + +struct KeyUpEvent { + Key key; +}; + +struct CharInputEvent { + uint32_t codepoint; +}; + +struct CursorPosEvent { + double x; + double y; +}; + +struct ScrollEvent { + double dx; + double dy; +}; + +enum MouseButton { + MOUSE_BUTTON_LEFT, + MOUSE_BUTTON_RIGHT, + MOUSE_BUTTON_MIDDLE +}; + +struct MouseEvent { + MouseButton button; + bool state; + double x; + double y; +}; + +} + +class WindowImpl: public EventTarget, public ClientSubsystem { +public: + virtual bool shouldClose() = 0; + virtual WGPUSurface createWGPUSurface(WGPUInstance instance) = 0; + virtual void setPointerLock() = 0; + virtual void releasePointerLock() = 0; + virtual bool isKeyDown(Key key) = 0; +}; + +class Window: public WindowImpl { +private: + GLFWwindow * window = nullptr; +public: + Window(uint32_t width, uint32_t height, std::string title); + + void render() override; + + WGPUSurface createWGPUSurface(WGPUInstance instance) override; + void setTitle(std::string title); + bool shouldClose() override; + void setPointerLock() override; + void releasePointerLock() override; + bool isKeyDown(Key key) override; +}; + +} diff --git a/Client/glfw3webgpu/.gitignore b/Client/glfw3webgpu/.gitignore new file mode 100644 index 0000000..7e78c36 --- /dev/null +++ b/Client/glfw3webgpu/.gitignore @@ -0,0 +1 @@ +releases/ diff --git a/Client/glfw3webgpu/CMakeLists.txt b/Client/glfw3webgpu/CMakeLists.txt new file mode 100644 index 0000000..680d0e8 --- /dev/null +++ b/Client/glfw3webgpu/CMakeLists.txt @@ -0,0 +1,15 @@ +# This is only meant to be included as a subdirectory in another project. +# It assumes that targets 'glfw' and 'webgpu' exist. +# Look at examples/CMakeLists.txt to see how to use it in a project. + +# The glfw3webgpu target +add_library(glfw3webgpu STATIC glfw3webgpu.c) +target_include_directories(glfw3webgpu PUBLIC .) +target_link_libraries(glfw3webgpu PUBLIC glfw webgpu) + +# Copy compile definitions that are PRIVATE in glfw +if (GLFW_BUILD_COCOA) + target_compile_definitions(glfw3webgpu PRIVATE _GLFW_COCOA) + target_compile_options(glfw3webgpu PRIVATE -x objective-c) + target_link_libraries(glfw3webgpu PRIVATE "-framework Cocoa" "-framework CoreVideo" "-framework IOKit" "-framework QuartzCore") +endif() diff --git a/Client/glfw3webgpu/LICENSE.txt b/Client/glfw3webgpu/LICENSE.txt new file mode 100644 index 0000000..e3a58e9 --- /dev/null +++ b/Client/glfw3webgpu/LICENSE.txt @@ -0,0 +1,20 @@ +MIT License +Copyright (c) 2022-2024 Élie Michel and the wgpu-native authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Client/glfw3webgpu/README.md b/Client/glfw3webgpu/README.md new file mode 100644 index 0000000..538c7f6 --- /dev/null +++ b/Client/glfw3webgpu/README.md @@ -0,0 +1,94 @@ +CMake Badge + +
+ + + + Learn WebGPU Logo + + + LearnWebGPU  |  WebGPU-C++  |  WebGPU-distribution
+ glfw3webgpu  |  sdl2webgpu  |  sdl3webgpu + + Discord | Join us! +
+ + +GLFW WebGPU Extension +===================== + +This is an extension for the great [GLFW](https://www.glfw.org/) library for using it with **WebGPU native**. It was written as part of the [Learn WebGPU for native C++](https://eliemichel.github.io/LearnWebGPU) tutorial series. + +Table of Contents +----------------- + + - [Overview](#overview) + - [Usage](#usage) + - [Example](#example) + - [License](#license) + +Overview +-------- + +This extension simply provides the following function: + +```C +WGPUSurface glfwCreateWindowWGPUSurface(WGPUInstance instance, GLFWwindow* window); +``` + +Given a GLFW window, `glfwCreateWindowWGPUSurface` returns a WebGPU *surface* that corresponds to the window's back-end. This is a process that is highly platform-specific, which is why I believe it belongs to GLFW. + +Usage +----- + +**NB** The current version of this extension is written for GLFW 3.4. Up to version 1.2.0, it was written for GLFW 3.3.8. + +Your project must link to an implementation of WebGPU (providing `webgpu.h`) and of course to GLFW. Then: + +**Option A** If you use CMake, you can simply include this project as a subdirectory with `add_subdirectory(glfw3webgpu)` (see the content of [`CMakeLists.txt`](CMakeLists.txt)). + +**Option B** Just copy [`glfw3webgpu.h`](glfw3webgpu.h) and [`glfw3webgpu.c`](glfw3webgpu.c) to your project's source tree. On MacOS, you must add the compile option `-x objective-c` and the link libraries `-framework Cocoa`, `-framework CoreVideo`, `-framework IOKit`, and `-framework QuartzCore`. + +Example +------- + +Thanks to this extension it is possible to simply write a fully cross-platform WebGPU hello world: + +```C +#include "glfw3webgpu.h" + +#define GLFW_INCLUDE_NONE +#include +#include +#include + +int main(int argc, char* argv[]) { + // Init WebGPU + WGPUInstanceDescriptor desc; + desc.nextInChain = NULL; + WGPUInstance instance = wgpuCreateInstance(&desc); + + // Init GLFW + glfwInit(); + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + GLFWwindow* window = glfwCreateWindow(640, 480, "Learn WebGPU", NULL, NULL); + + // Here we create our WebGPU surface from the window! + WGPUSurface surface = glfwCreateWindowWGPUSurface(instance, window); + printf("surface = %p", surface); + + // Terminate GLFW + while (!glfwWindowShouldClose(window)) glfwPollEvents(); + glfwDestroyWindow(window); + glfwTerminate(); + + return 0; +} +``` + +**NB** The linking process depends on the implementation of WebGPU that you are using. You can find detailed instructions for the `wgpu-native` implementation in [this Hello WebGPU chapter](https://eliemichel.github.io/LearnWebGPU/getting-started/hello-webgpu.html). You may also check out [`examples/CMakeLists.txt`](examples/CMakeLists.txt). + +License +------- + +See [LICENSE.txt](LICENSE.txt). diff --git a/Client/glfw3webgpu/glfw3webgpu.c b/Client/glfw3webgpu/glfw3webgpu.c new file mode 100644 index 0000000..4eae623 --- /dev/null +++ b/Client/glfw3webgpu/glfw3webgpu.c @@ -0,0 +1,191 @@ +/** + * This is an extension of GLFW for WebGPU, abstracting away the details of + * OS-specific operations. + * + * This file is part of the "Learn WebGPU for C++" book. + * https://eliemichel.github.io/LearnWebGPU + * + * Most of this code comes from the wgpu-native triangle example: + * https://github.com/gfx-rs/wgpu-native/blob/master/examples/triangle/main.c + * + * MIT License + * Copyright (c) 2022-2025 Elie Michel and the wgpu-native authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "glfw3webgpu.h" + +#include + +#include + +// Begin hack +#if defined(__APPLE__) + #define _GLFW_COCOA +#elif defined(_WIN32) + #define _GLFW_WIN32 +#elif defined(__linux__) + #define _GLFW_X11 // or _GLFW_WAYLAND if you built GLFW with Wayland +#endif +// End hack + +#ifdef __EMSCRIPTEN__ +# define GLFW_EXPOSE_NATIVE_EMSCRIPTEN +# ifndef GLFW_PLATFORM_EMSCRIPTEN // not defined in older versions of emscripten +# define GLFW_PLATFORM_EMSCRIPTEN 0 +# endif +#else // __EMSCRIPTEN__ +# ifdef _GLFW_X11 +# define GLFW_EXPOSE_NATIVE_X11 +# endif +# ifdef _GLFW_WAYLAND +# define GLFW_EXPOSE_NATIVE_WAYLAND +# endif +# ifdef _GLFW_COCOA +# define GLFW_EXPOSE_NATIVE_COCOA +# endif +# ifdef _GLFW_WIN32 +# define GLFW_EXPOSE_NATIVE_WIN32 +# endif +#endif // __EMSCRIPTEN__ + +#ifdef GLFW_EXPOSE_NATIVE_COCOA +# include +# include +#endif + +#ifndef __EMSCRIPTEN__ +# include +#endif + +WGPUSurface glfwCreateWindowWGPUSurface(WGPUInstance instance, GLFWwindow* window) { +#ifndef __EMSCRIPTEN__ + switch (glfwGetPlatform()) { +#else + // glfwGetPlatform is not available in older versions of emscripten + switch (GLFW_PLATFORM_EMSCRIPTEN) { +#endif + +#ifdef GLFW_EXPOSE_NATIVE_X11 + case GLFW_PLATFORM_X11: { + Display* x11_display = glfwGetX11Display(); + Window x11_window = glfwGetX11Window(window); + + WGPUSurfaceSourceXlibWindow fromXlibWindow; + fromXlibWindow.chain.sType = WGPUSType_SurfaceSourceXlibWindow; + fromXlibWindow.chain.next = NULL; + fromXlibWindow.display = x11_display; + fromXlibWindow.window = x11_window; + + WGPUSurfaceDescriptor surfaceDescriptor; + surfaceDescriptor.nextInChain = &fromXlibWindow.chain; + surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN }; + + return wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } +#endif // GLFW_EXPOSE_NATIVE_X11 + +#ifdef GLFW_EXPOSE_NATIVE_WAYLAND + case GLFW_PLATFORM_WAYLAND: { + struct wl_display* wayland_display = glfwGetWaylandDisplay(); + struct wl_surface* wayland_surface = glfwGetWaylandWindow(window); + + WGPUSurfaceSourceWaylandSurface fromWaylandSurface; + fromWaylandSurface.chain.sType = WGPUSType_SurfaceSourceWaylandSurface; + fromWaylandSurface.chain.next = NULL; + fromWaylandSurface.display = wayland_display; + fromWaylandSurface.surface = wayland_surface; + + WGPUSurfaceDescriptor surfaceDescriptor; + surfaceDescriptor.nextInChain = &fromWaylandSurface.chain; + surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN }; + + return wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } +#endif // GLFW_EXPOSE_NATIVE_WAYLAND + +#ifdef GLFW_EXPOSE_NATIVE_COCOA + case GLFW_PLATFORM_COCOA: { + id metal_layer = [CAMetalLayer layer]; + NSWindow* ns_window = glfwGetCocoaWindow(window); + [ns_window.contentView setWantsLayer : YES] ; + [ns_window.contentView setLayer : metal_layer] ; + + WGPUSurfaceSourceMetalLayer fromMetalLayer; + fromMetalLayer.chain.sType = WGPUSType_SurfaceSourceMetalLayer; + fromMetalLayer.chain.next = NULL; + fromMetalLayer.layer = (__bridge void *)(metal_layer); + + WGPUSurfaceDescriptor surfaceDescriptor; + surfaceDescriptor.nextInChain = &fromMetalLayer.chain; + surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN }; + + return wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } +#endif // GLFW_EXPOSE_NATIVE_COCOA + +#ifdef GLFW_EXPOSE_NATIVE_WIN32 + case GLFW_PLATFORM_WIN32: { + HWND hwnd = glfwGetWin32Window(window); + HINSTANCE hinstance = GetModuleHandle(NULL); + + WGPUSurfaceSourceWindowsHWND fromWindowsHWND; + fromWindowsHWND.chain.sType = WGPUSType_SurfaceSourceWindowsHWND; + fromWindowsHWND.chain.next = NULL; + fromWindowsHWND.hinstance = hinstance; + fromWindowsHWND.hwnd = hwnd; + + WGPUSurfaceDescriptor surfaceDescriptor; + surfaceDescriptor.nextInChain = &fromWindowsHWND.chain; + surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN }; + + return wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } +#endif // GLFW_EXPOSE_NATIVE_WIN32 + +#ifdef GLFW_EXPOSE_NATIVE_EMSCRIPTEN + case GLFW_PLATFORM_EMSCRIPTEN: { +# ifdef WEBGPU_BACKEND_EMDAWNWEBGPU + WGPUEmscriptenSurfaceSourceCanvasHTMLSelector fromCanvasHTMLSelector; + fromCanvasHTMLSelector.chain.sType = WGPUSType_EmscriptenSurfaceSourceCanvasHTMLSelector; + fromCanvasHTMLSelector.selector = (WGPUStringView){ "canvas", WGPU_STRLEN }; +# else + WGPUSurfaceDescriptorFromCanvasHTMLSelector fromCanvasHTMLSelector; + fromCanvasHTMLSelector.chain.sType = WGPUSType_SurfaceDescriptorFromCanvasHTMLSelector; + fromCanvasHTMLSelector.selector = "canvas"; +# endif + fromCanvasHTMLSelector.chain.next = NULL; + + WGPUSurfaceDescriptor surfaceDescriptor; + surfaceDescriptor.nextInChain = &fromCanvasHTMLSelector.chain; +# ifdef WEBGPU_BACKEND_EMDAWNWEBGPU + surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN }; +# else + surfaceDescriptor.label = NULL; +# endif + return wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } +#endif // GLFW_EXPOSE_NATIVE_EMSCRIPTEN + + default: + // Unsupported platform + return NULL; + } +} diff --git a/Client/glfw3webgpu/glfw3webgpu.h b/Client/glfw3webgpu/glfw3webgpu.h new file mode 100644 index 0000000..2abbd8c --- /dev/null +++ b/Client/glfw3webgpu/glfw3webgpu.h @@ -0,0 +1,62 @@ +/** + * This is an extension of GLFW for WebGPU, abstracting away the details of + * OS-specific operations. + * + * This file is part of the "Learn WebGPU for C++" book. + * https://eliemichel.github.io/LearnWebGPU + * + * MIT License + * Copyright (c) 2022-2024 Elie Michel and the wgpu-native authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _glfw3_webgpu_h_ +#define _glfw3_webgpu_h_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/*! @brief Creates a WebGPU surface for the specified window. + * + * This function creates a WGPUSurface object for the specified window. + * + * If the surface cannot be created, this function returns `NULL`. + * + * It is the responsibility of the caller to destroy the window surface. The + * window surface must be destroyed using `wgpuSurfaceRelease`. + * + * @param[in] instance The WebGPU instance to create the surface in. + * @param[in] window The window to create the surface for. + * @return The handle of the surface. This is set to `NULL` if an error + * occurred. + * + * @ingroup webgpu + */ +WGPUSurface glfwCreateWindowWGPUSurface(WGPUInstance instance, GLFWwindow* window); + +#ifdef __cplusplus +} +#endif + +#endif // _glfw3_webgpu_h_ diff --git a/README.md b/README.md new file mode 100644 index 0000000..41d5b5f --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ + +# What is this? + +Artifact Engine is a game engine designed to make creating a voxel game as simple as possible. + +# Status + +The engine is currently in very early development. The only thing it really has is a basic organizational structure and a working UI renderer. + +# Testing + +Testing on non-macOS platforms is currently somewhat tricky, since the build system is currently Xcode. A CMake script is planned, but currently Xcode is much easier to use while the engine is being created. diff --git a/Server/CMakeLists.txt b/Server/CMakeLists.txt new file mode 100644 index 0000000..b1f2ecd --- /dev/null +++ b/Server/CMakeLists.txt @@ -0,0 +1,6 @@ +file(GLOB_RECURSE MY_SOURCES CONFIGURE_DEPENDS + "*.cpp" + "*.h" +) + +add_library(Server ${MY_SOURCES}) diff --git a/Server/Network.h b/Server/Network.h new file mode 100644 index 0000000..45d27d3 --- /dev/null +++ b/Server/Network.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include "Server.h" + +namespace Artifact { + +class ServerNetwork: public NetworkServer, public ServerSubsystem {}; + +} diff --git a/Server/Player.cpp b/Server/Player.cpp new file mode 100644 index 0000000..94dcb6f --- /dev/null +++ b/Server/Player.cpp @@ -0,0 +1,7 @@ +#include "Player.h" + +namespace Artifact { + + + +} diff --git a/Server/Player.h b/Server/Player.h new file mode 100644 index 0000000..b3693af --- /dev/null +++ b/Server/Player.h @@ -0,0 +1,9 @@ +#pragma once + +namespace Artifact { + +class Player { + +}; + +} diff --git a/Server/Server.cpp b/Server/Server.cpp new file mode 100644 index 0000000..5d77685 --- /dev/null +++ b/Server/Server.cpp @@ -0,0 +1,37 @@ +#include "Server.h" +#include "Network.h" +#include "World/WorldManager.h" + +namespace Artifact { + +void Server::init() { + for (auto & system : subsystems) { + system->init(); + system->reload(); + } +} + +void Server::tick() { + for (auto & system : subsystems) { + system->tick(); + } +} + +void Server::run() { + init(); + + auto time = std::chrono::steady_clock::now(); + while (true) { + auto now = std::chrono::steady_clock::now(); + if (time - now > std::chrono::milliseconds(50)) { + tick(); + } + } +} + +void Server::addDefaultSubsystems() { + addSubsystem(); + addSubsystem(); +} + +} diff --git a/Server/Server.h b/Server/Server.h new file mode 100644 index 0000000..3fd6cf7 --- /dev/null +++ b/Server/Server.h @@ -0,0 +1,28 @@ +#pragma once + +#include "Shared.h" +#include + +namespace Artifact { + +class ServerSubsystem; + +class Server: public Engine { +public: + Settings settings; + + Server() : settings(getServerConfigPath()) {} + + void init(); + void tick(); + void run(); + void addDefaultSubsystems(); +}; + +class ServerSubsystem: public BaseSubsystem { +public: + Server * server = nullptr; + virtual void tick() {} +}; + +} diff --git a/Server/World/WorldBackend.cpp b/Server/World/WorldBackend.cpp new file mode 100644 index 0000000..611d601 --- /dev/null +++ b/Server/World/WorldBackend.cpp @@ -0,0 +1,7 @@ +// +// WorldBackend.cpp +// ArtifactEngine +// +// Created by Isaac Boettcher on 3/11/26. +// + diff --git a/Server/World/WorldBackend.h b/Server/World/WorldBackend.h new file mode 100644 index 0000000..7206be6 --- /dev/null +++ b/Server/World/WorldBackend.h @@ -0,0 +1,10 @@ +#pragma once + +namespace Artifact { + +class WorldBackend { + virtual void saveChunk(); + virtual void loadChukn(); +}; + +} diff --git a/Server/World/WorldManager.cpp b/Server/World/WorldManager.cpp new file mode 100644 index 0000000..e2e4e43 --- /dev/null +++ b/Server/World/WorldManager.cpp @@ -0,0 +1,11 @@ +#include "WorldManager.h" + +#include + +namespace Artifact { + +void WorldManager::tick() { + +} + +} diff --git a/Server/World/WorldManager.h b/Server/World/WorldManager.h new file mode 100644 index 0000000..9e58f4c --- /dev/null +++ b/Server/World/WorldManager.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include "../Server.h" + +namespace Artifact { + +class WorldManagerImpl: public ServerSubsystem { + +}; + +class WorldManager: public WorldManagerImpl { + void tick() override; +}; + +} diff --git a/Shared/CMakeLists.txt b/Shared/CMakeLists.txt new file mode 100644 index 0000000..51006c1 --- /dev/null +++ b/Shared/CMakeLists.txt @@ -0,0 +1,6 @@ +file(GLOB_RECURSE MY_SOURCES CONFIGURE_DEPENDS + "*.cpp" + "*.h" +) + +add_library(Shared ${MY_SOURCES}) diff --git a/Shared/Events.h b/Shared/Events.h new file mode 100644 index 0000000..14a7c31 --- /dev/null +++ b/Shared/Events.h @@ -0,0 +1,91 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +class EventTarget { +public: + template + using Callback = std::function; + + // Subscribe to an event type. + template + uint64_t listen(Callback callback) { + auto type = std::type_index(typeid(T)); + auto & vec = callbacks[type]; + + uint64_t id = nextEventId++; + + vec.emplace_back([cb = std::move(callback)](const std::any & e) { + cb(std::any_cast(e)); + }); + return id; + } + + // Unsubscribe a specific listener. + template + void unlisten(uint64_t id) { + _unlisten(std::type_index(typeid(T)), id); + } + + // Synchronously dispatch an event. + template + void dispatch(const T & event) { + auto it = callbacks.find(std::type_index(typeid(T))); + if (it == callbacks.end()) return; + + // Copy the list in case a callback unsubscribes during dispatch. + auto copy = it->second; + for (const auto & cb : copy) { + cb(event); + } + } + + // Should be called on a main loop to dispatch queued events. + void processQueue() { + for (auto & [anyEvent, copy] : queued) { + auto it = callbacks.find(anyEvent.type()); + if (it != callbacks.end()) { + for (const auto & cb : copy) { + cb(anyEvent); + } + } + } + queued.clear(); + } + + // Queue an event to be dispatched on the next call to `processQueue`. + template + void queue(const T & event) { + auto type = std::type_index(typeid(T)); + auto it = callbacks.find(type); + if (it == callbacks.end()) return; + queued.emplace_back(std::any(event), it->second); + } + +private: + using AnyCallback = std::function; + using CallbackWithId = std::pair; + + std::unordered_map> callbacks {}; + std::vector>> queued {}; + + uint64_t nextEventId = 0; + + void _unlisten(std::type_index type, uint64_t id) { +// auto it = callbacks.find(type); +// if (it == callbacks.end()) return; +// +// auto & vec = it->second; +// auto erase_it = std::find_if(vec.begin(), vec.end(), +// [id](const auto& p) { return p.second == id; }); +// +// if (erase_it != vec.end()) { +// *erase_it = std::move(vec.back()); +// vec.pop_back(); +// } + } +}; diff --git a/Shared/Network/Connection.cpp b/Shared/Network/Connection.cpp new file mode 100644 index 0000000..fbab8a4 --- /dev/null +++ b/Shared/Network/Connection.cpp @@ -0,0 +1,148 @@ +#include +#include + +#include +#include +#include +#include + +#include "Connection.h" + +namespace Artifact { + +Connection::Connection() { + ngtcp2_settings_default(&settings); + ngtcp2_transport_params_default(&transportParams); + transportParams.initial_max_streams_bidi = 1; + transportParams.initial_max_data = 65535; + transportParams.initial_max_stream_data_bidi_local = 65535; + + callbacks.encrypt = ngtcp2_crypto_encrypt_cb; + callbacks.decrypt = ngtcp2_crypto_decrypt_cb; + callbacks.hp_mask = ngtcp2_crypto_hp_mask_cb; + callbacks.update_key = ngtcp2_crypto_update_key_cb; + callbacks.delete_crypto_aead_ctx = ngtcp2_crypto_delete_crypto_aead_ctx_cb; + callbacks.delete_crypto_cipher_ctx = ngtcp2_crypto_delete_crypto_cipher_ctx_cb; + callbacks.get_path_challenge_data = ngtcp2_crypto_get_path_challenge_data_cb; + callbacks.rand = [](auto dest, auto len, auto ctx) { + gnutls_rnd(GNUTLS_RND_RANDOM, dest, len); + }; + callbacks.get_new_connection_id = [](auto conn, auto cid, auto token, auto cidlen, auto userdata) { + gnutls_rnd(GNUTLS_RND_RANDOM, cid, cidlen); + gnutls_rnd(GNUTLS_RND_RANDOM, token, NGTCP2_STATELESS_RESET_TOKENLEN); + return 0; + }; + callbacks.version_negotiation = ngtcp2_crypto_version_negotiation_cb; + + gnutls_rnd(GNUTLS_RND_RANDOM, dcid.data, dcid.datalen); + gnutls_rnd(GNUTLS_RND_RANDOM, scid.data, scid.datalen); +} + +void readPacket(ngtcp2_pkt_info * info, void * packet) { + +} + +ClientConnection::ClientConnection(std::string host, std::string port) { + struct addrinfo hints; + struct addrinfo * local = nullptr; + struct addrinfo * remote = nullptr; + getaddrinfo("::", "", &hints, &local); + getaddrinfo(host.c_str(), port.c_str(), &hints, &remote); + ngtcp2_path path = { + .local = { + .addr = local->ai_addr, + .addrlen = local->ai_addrlen + }, + .remote = { + .addr = remote->ai_addr, + .addrlen = remote->ai_addrlen + } + }; + + if (gnutls_init(&session, GNUTLS_CLIENT) != GNUTLS_E_SUCCESS) { + throw std::runtime_error("Failed to initialize gnutls."); + } + gnutls_set_default_priority(session); + + ngtcp2_conn_client_new(&conn, &dcid, &scid, &path, NGTCP2_PROTO_VER_V1, &callbacks, &settings, &transportParams, nullptr, this); + ngtcp2_conn_set_tls_native_handle(conn, session); +} + +ServerConnection::ServerConnection(ngtcp2_path path) : path(path) { + if (gnutls_init(&session, GNUTLS_CLIENT) != GNUTLS_E_SUCCESS) { + throw std::runtime_error("Failed to initialize gnutls."); + } + gnutls_set_default_priority(session); + + ngtcp2_conn_server_new(&conn, &dcid, &scid, &path, NGTCP2_PROTO_VER_V1, &callbacks, &settings, &transportParams, nullptr, this); + ngtcp2_conn_set_tls_native_handle(conn, session); +} + +ServerListener::ServerListener(std::string port) { + struct addrinfo hints; + struct addrinfo * local = nullptr; + getaddrinfo("::", "", &hints, &local); + + socketDescriptor = socket(local->ai_family, local->ai_socktype, local->ai_protocol); + + int on = 1; + setsockopt(socketDescriptor, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&on), sizeof(int)); + + if (local->ai_family == AF_INET6) { + int off = 0; + setsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&off), sizeof(int)); + } + + if (bind(socketDescriptor, local->ai_addr, local->ai_addrlen)) { + // Save our actual local address for later use. + getsockname(socketDescriptor, reinterpret_cast(&localAddr.addr), &localAddr.addrlen); + } +} + +void ServerListener::onReadable(struct ev_loop *, ev_io * watcher, int) { + auto self = static_cast(watcher->data); + + uint8_t buf[2048]; + ngtcp2_addr remote; + auto len = recvfrom(self->socketDescriptor, buf, sizeof(buf), 0, remote.addr, &remote.addrlen); + + ngtcp2_pkt_info pi{}; + ngtcp2_pkt_hd hd{}; + ngtcp2_pkt_decode_hd_long(&hd, buf, len); + + auto it = self->connections.find(hd.dcid); + if (it != self->connections.end()) { + ServerConnection * conn = it->second.get(); + + ngtcp2_conn_read_pkt(conn->conn, &conn->path, &pi, buf, len, std::chrono::steady_clock::now().time_since_epoch().count()); + + conn->readPacket(&pi, buf); + return; + } + + if (hd.flags & NGTCP2_PKT_FLAG_LONG_FORM && hd.type == NGTCP2_PKT_INITIAL) { + ngtcp2_pkt_hd accept_hd{}; + if (ngtcp2_accept(&accept_hd, buf, static_cast(len)) != 0) { + return; + } + + auto new_conn = std::make_unique(ngtcp2_path { + .local = self->localAddr, + .remote = remote + }); + + self->connections[accept_hd.dcid] = std::move(new_conn); + } +} + +void ServerListener::run() { + std::thread([this]() { + loop = ev_loop_new(EVFLAG_AUTO); + ev_io_init(&watcher, onReadable, socketDescriptor, EV_READ); + watcher.data = this; + ev_io_start(loop, &watcher); + ev_run(loop, 0); + }).detach(); +} + +} diff --git a/Shared/Network/Connection.h b/Shared/Network/Connection.h new file mode 100644 index 0000000..98e8eda --- /dev/null +++ b/Shared/Network/Connection.h @@ -0,0 +1,95 @@ +#pragma once + +#include +#include + +#include +#include +#include + +// Platform-specific socket includes and helpers +#ifdef _WIN32 + #include + #include + #pragma comment(lib, "ws2_32.lib") + using socket_t = SOCKET; + #define CLOSE_SOCKET closesocket + #define SOCKLEN_T int + #define IS_INVALID_SOCKET(s) ((s) == INVALID_SOCKET) + #define GET_LAST_ERROR() WSAGetLastError() +#else + #include + #include + #include + #include + #include + #include + using socket_t = int; + #define CLOSE_SOCKET close + #define SOCKLEN_T socklen_t + #define IS_INVALID_SOCKET(s) ((s) < 0) + #define GET_LAST_ERROR() errno +#endif + +namespace Artifact { + +namespace { + +struct Ngtcp2CidEqual { + bool operator()(const ngtcp2_cid& a, const ngtcp2_cid& b) const noexcept { + return a.datalen == b.datalen && + std::memcmp(a.data, b.data, a.datalen) == 0; + } +}; + +struct Ngtcp2CidHash { + size_t operator()(const ngtcp2_cid& cid) const noexcept { + size_t h = cid.datalen; + for (uint8_t i = 0; i < cid.datalen; ++i) { + h = (h * 31) ^ cid.data[i]; // or better hash as above + } + return h; + } +}; + +} + +class Connection { +public: + ngtcp2_settings settings; + ngtcp2_transport_params transportParams; + ngtcp2_callbacks callbacks; + ngtcp2_cid dcid, scid; + gnutls_session_t session; + ngtcp2_conn * conn; + + Connection(); + + void readPacket(ngtcp2_pkt_info * info, void * packet); +}; + +class ClientConnection: public Connection { +public: + ClientConnection(std::string host, std::string port); +}; + +class ServerConnection: public Connection { +public: + ngtcp2_path path; + ServerConnection(ngtcp2_path path); +}; + +class ServerListener { + socket_t socketDescriptor; + std::unordered_map, Ngtcp2CidHash, Ngtcp2CidEqual> connections; + ngtcp2_addr localAddr; + + struct ev_loop * loop; + ev_io watcher; +public: + ServerListener(std::string port); + static void onReadable(struct ev_loop *, ev_io * watcher, int); + void run(); +}; + +} diff --git a/Shared/Network/Network.cpp b/Shared/Network/Network.cpp new file mode 100644 index 0000000..8913b6f --- /dev/null +++ b/Shared/Network/Network.cpp @@ -0,0 +1,31 @@ +#include "Network.h" + +namespace Artifact { + +void NetworkServer::host(NetworkClient * local) { + if (active) { + unhost(); + } + + active = true; + localClient = local; + listener = local->listen([](auto ev) { + + }); +} + +void NetworkServer::unhost() { + active = false; + if (localClient) { + localClient->unlisten(listener); + localClient = nullptr; + } else if (server) { + server = nullptr; + } +} + +void NetworkClient::connect(NetworkServer * local) { + localServer = local; +} + +} diff --git a/Shared/Network/Network.h b/Shared/Network/Network.h new file mode 100644 index 0000000..7497222 --- /dev/null +++ b/Shared/Network/Network.h @@ -0,0 +1,45 @@ +#pragma once + +#include "Shared.h" +#include "Events.h" +#include "Connection.h" + +namespace Artifact { + +namespace Events { + +struct NetworkMessage { + void * data; +}; + +} + +class NetworkClient; + +class NetworkServer: public BaseSubsystem, public EventTarget { + NetworkClient * localClient = nullptr; + std::unique_ptr server = nullptr; + bool active = false; + uint64_t listener; +public: + + void host(NetworkClient * client); + void host(std::string port); + + void unhost(); +}; + +class NetworkClient: public BaseSubsystem, public EventTarget { + NetworkServer * localServer = nullptr; + std::unique_ptr server = nullptr; + bool active = false; + uint64_t listener; +public: + + void connect(NetworkServer * local); + void connect(std::string host, std::string port); + + void disconnect(); +}; + +} diff --git a/Shared/Paths.cpp b/Shared/Paths.cpp new file mode 100644 index 0000000..9ae3359 --- /dev/null +++ b/Shared/Paths.cpp @@ -0,0 +1,60 @@ +#include "Paths.h" + +namespace Artifact { + +Path getDataPath() { + Path base; + + // ──────────────────────────────────────────────── + // 1. Windows — AppData/Roaming (most common choice) + // ──────────────────────────────────────────────── +#ifdef _WIN32 + const char* appdata = std::getenv("APPDATA"); + if (!appdata || !fs::exists(appdata)) { + throw std::runtime_error("Cannot find APPDATA environment variable"); + } + base = Path(appdata) / organization / GAME; + +#elif defined(__APPLE__) + // ──────────────────────────────────────────────── + // 2. macOS — Application Support + // ──────────────────────────────────────────────── + const char* home = std::getenv("HOME"); + if (!home) throw std::runtime_error("Cannot find HOME"); + base = Path(home) / "Library" / "Application Support" / GAME; + +#else + // ──────────────────────────────────────────────── + // 3. Linux / BSD / Steam Deck — XDG_DATA_HOME + // ──────────────────────────────────────────────── + const char* xdg_data = std::getenv("XDG_DATA_HOME"); + if (xdg_data && *xdg_data) { + base = fs::path(xdg_data) / game; + } else { + const char* home = std::getenv("HOME"); + if (!home) throw std::runtime_error("Cannot find HOME"); + base = Path(home) / ".local" / "share" / GAME; + } +#endif + + // Create folder structure if it doesn't exist + std::error_code ec; + std::filesystem::create_directories(base, ec); + + if (ec) { + throw std::runtime_error("Failed to create directory: " + + base.string() + " → " + ec.message()); + } + + return base; +} + +Path getClientConfigPath() { + return getDataPath() / "client.conf"; +} + +Path getServerConfigPath() { + return getDataPath() / "server.conf"; +} + +} diff --git a/Shared/Paths.h b/Shared/Paths.h new file mode 100644 index 0000000..ebebed7 --- /dev/null +++ b/Shared/Paths.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +#ifndef APP_NAME +# define APP_NAME ArtifactEngine +#endif + +#define TOSTRING(x) #x + +#define GAME TOSTRING(APP_NAME) + +namespace Artifact { + +using Path = std::filesystem::path; + +Path getDataPath(); + +Path getClientConfigPath(); + +Path getServerConfigPath(); + +} diff --git a/Shared/Settings.cpp b/Shared/Settings.cpp new file mode 100644 index 0000000..4e6a84c --- /dev/null +++ b/Shared/Settings.cpp @@ -0,0 +1,58 @@ +#include "Settings.h" + +namespace Artifact { + +Settings::Settings() : Settings(getDataPath() / "settings.conf") {} + +Settings::Settings(Path path) : path(path) { + reload(); +} + +std::string trim(const std::string& str) { + size_t first = str.find_first_not_of(" \t\r\n"); + if (first == std::string::npos) return ""; + size_t last = str.find_last_not_of(" \t\r\n"); + return str.substr(first, last - first + 1); +} + +void Settings::reload() { + clear(); + std::ifstream file(path); + if (!file.is_open()) { + // TODO: Log a warning + return; + } + + std::string line; + while (std::getline(file, line)) { + line = trim(line); + if (line.empty() || line[0] == '#' || line[0] == ';') { + continue; + } + + size_t eq_pos = line.find('='); + if (eq_pos == std::string::npos) { + continue; + } + + std::string key = trim(line.substr(0, eq_pos)); + std::string value = trim(line.substr(eq_pos + 1)); + + if (!key.empty()) { + data[key] = value; + } + } +} + +void Settings::save() { + std::ofstream file(path); + if (!file.is_open()) { + return; + } + + for (const auto & [key, value] : data) { + file << key << " = " << value << '\n'; + } +} + +} diff --git a/Shared/Settings.h b/Shared/Settings.h new file mode 100644 index 0000000..96c03ca --- /dev/null +++ b/Shared/Settings.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include +#include +#include + +#include "Paths.h" +#include "Events.h" + +namespace Artifact { + +namespace Events { + +struct SettingChanged { + std::string key; + std::optional value; +}; + +} + +class Settings: public EventTarget { + const Path path; + std::unordered_map data; +public: + Settings(); + Settings(Path path); + + void reload(); + void save(); + + template + T get(std::string & key, const T & fallback = {}) { + auto it = data.find(key); + if (it == data.end()) { + return fallback; + } + + const std::string & str = it->second; + + if constexpr (std::is_same_v) { + return str; + } + + if constexpr (std::is_same_v) { + std::string lower = str; + std::transform(lower.begin(), lower.end(), lower.begin(), + [](unsigned char c){ return static_cast(std::tolower(c)); }); + if (lower == "true" || lower == "1" || lower == "yes" || lower == "on") { + return true; + } + if (lower == "false" || lower == "0" || lower == "no" || lower == "off") { + return false; + } + return fallback; + } + + std::istringstream iss(str); + T value; + if (iss >> value) { + return value; + } + + return fallback; + } + + template + void set(const std::string & key, const T & value) { + std::ostringstream oss; + oss << std::boolalpha << value; + data[key] = oss.str(); + dispatch(Events::SettingChanged { .key = key, .value = value }); + } + + void reset(const std::string & key) { + data.erase(key); + dispatch(Events::SettingChanged { .key = key, .value = std::nullopt }); + } + + void clear() { + data.clear(); + } +}; + +} diff --git a/Shared/Shared.cpp b/Shared/Shared.cpp new file mode 100644 index 0000000..901b81b --- /dev/null +++ b/Shared/Shared.cpp @@ -0,0 +1,11 @@ +#include + +#include "Shared.h" + +namespace Artifact { + +void log(std::string msg) { + std::cout << msg << std::endl; +} + +} diff --git a/Shared/Shared.h b/Shared/Shared.h new file mode 100644 index 0000000..62d745d --- /dev/null +++ b/Shared/Shared.h @@ -0,0 +1,147 @@ +#pragma once + +#include + +#include + +namespace std { + +template<> +struct hash { + std::size_t operator()(const glm::ivec3& v) const noexcept { + // Very common and reasonably good quality combination for floats + // (tries to mix bits while being fast) + + std::size_t seed = 0; + + // You can use any of these styles — pick one: + + // ──────────────────────────────────────────────────────────────── + // Style 1: Boost hash_combine pattern (very popular) + auto hash_float = [](float f) -> std::size_t { + return std::hash{}(f); + }; + + seed ^= hash_float(v.x) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= hash_float(v.y) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= hash_float(v.z) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + + // ──────────────────────────────────────────────────────────────── + // Style 2: Simpler — fold with multiplication (often enough) + // seed = std::hash{}(v.x); + // seed = seed * 31 + std::hash{}(v.y); + // seed = seed * 31 + std::hash{}(v.z); + + // ──────────────────────────────────────────────────────────────── + // Style 3: Treat as 12-byte blob → good quality, but slower on some platforms + // std::size_t h = 0; + // std::memcpy(&h, &v, sizeof(float)); // x + // h ^= std::hash{}(h); + // std::memcpy(&h, reinterpret_cast(&v) + 4, sizeof(float)); + // h ^= std::hash{}(h) * 0x85ebca6b; + // std::memcpy(&h, reinterpret_cast(&v) + 8, sizeof(float)); + // return std::hash{}(h); + + return seed; + } +}; + +} // namespace std + +namespace Artifact { + +template +class Engine { +public: + std::vector> subsystems; + + template + T * addSubsystem(Args &&... args) { + auto system = std::make_unique(std::forward(args)...); + auto ptr = system.get(); + subsystems.push_back(std::move(system)); + return ptr; + } + template + T * subsystem() const { + for (const auto & system : subsystems) { + if (T * ptr = dynamic_cast(system.get())) { + return ptr; + } + } + return nullptr; + } +}; + +class BaseSubsystem { +public: + virtual void init() {} + virtual void reload() {} + virtual void deinit() {} +}; + +template requires std::integral || std::floating_point +class Composed { + enum ModifierType { + Add, Multiply + }; + using Modifier = std::pair; + + uint64_t nextID = 0; + + std::vector modifiers; + T _value; +public: + Composed() : Composed(T{0}) {} + Composed(T base) { + add(Add, base); + } + + T value() { + return _value; + } + + void recompute() { + T sum; + T fac; + for (auto [type, value] : modifiers) { + if (type == Add) { + sum += value; + } else if (type == Multiply) { + fac *= value; + } + } + + _value = sum * fac; + } + + uint64_t add(ModifierType type, T value) { + modifiers.emplace_back(type, value); + recompute(); + return nextID++; + } + + bool remove(uint64_t id) { + auto it = std::find_if(modifiers.begin(), modifiers.end(), + [id](const Modifier & m) { return m.id == id; }); + if (it == modifiers.end()) return false; + + modifiers.erase(it); + recompute(); + return true; + } + + bool update(uint64_t id, T newValue) { + auto it = std::find_if(modifiers.begin(), modifiers.end(), + [id](const Modifier& m) { return m.id == id; }); + if (it == modifiers.end()) return false; + + it->value = newValue; + recompute(); + return true; + } +}; + +void log(std::string msg); + +} diff --git a/Shared/World/Chunk.h b/Shared/World/Chunk.h new file mode 100644 index 0000000..530fe3b --- /dev/null +++ b/Shared/World/Chunk.h @@ -0,0 +1,9 @@ +#include + +#include + +struct Chunk { + glm::ivec3 pos; + std::vector data; + bool dirty = false; +}; diff --git a/TestGame/main.cpp b/TestGame/main.cpp new file mode 100644 index 0000000..c503815 --- /dev/null +++ b/TestGame/main.cpp @@ -0,0 +1,52 @@ +#include +#include + +#include +#include +#include +#include + +using namespace Artifact; + +struct Args { + bool server = false; + std::string host = ""; + std::string port = ""; +}; + +int main(int argc, const char * argv[]) { + Args args {}; + for (int i = 0; i < argc; ++i) { + if (strcmp(argv[i], "--server")) { + args.server = true; + } else if (strcmp(argv[i], "--host")) { + args.host = argv[++i]; + } else if (strcmp(argv[i], "--port")) { + args.port = argv[++i]; + } + } + + getDataPath(); + + Client client; + + client.addDefaultSubsystems(); + + std::thread([&client]() { + Server server; + + server.addDefaultSubsystems(); + + auto serverListener = server.subsystem(); + auto clientConnection = client.subsystem(); + + serverListener->host(clientConnection); + clientConnection->connect(serverListener); + + server.run(); + }).detach(); + + client.run(); + + return 0; +}