diff --git a/BUILD.md b/BUILD.md
new file mode 100644
index 0000000..c7bcd5b
--- /dev/null
+++ b/BUILD.md
@@ -0,0 +1,2 @@
+# Build
+Do NOT build from this repository, due to limitations with putting scripts under ModuleScripts I've had to restructure the repository. If you build this repository and try to use it you'll only end up with errors. If you know how I could fix this please put a pull request or an issue ticket and I'll contact you via it. If you'd like to use Atom, please use a pre-built .rbxm under releases.
\ No newline at end of file
diff --git a/README.md b/README.md
index f3f0a94..9774b43 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,15 @@
# Atom Framework
-
+An easy to implement Roblox framework.
+
+## Introduction
+The Atom Framework is an open-source framework used for crazyattaker1's games on Roblox.
+The Atom Framework is easy to work with and encourages modular coding.
+Documentation can be found in the documentation/ folder.
+
+## Features
+Atom was designed to do all the heavy work for multi module codebases. Some of the features Atom contains to assist you as a developer are:
+* Services, Controllers and Components - Seperate server and client code by using specialized containers for each type and a special type for shared code called Components.
+* Libraries - Packages from the Atom registry to include extra functionality that you can implement in your own modules.
+* Out-of-the-box Networking - A custom developed network solution that doesn't rely on Roblox remotes.
+* Automatic error handling - Allow Atom to automatically deal with errors or tell it what to do when an error occurs.
+* Automatic Garbage Collection - Atom uses the Janitor module to clean up instances when they're no longer needed.
\ No newline at end of file
diff --git a/selene.toml b/selene.toml
new file mode 100644
index 0000000..c4ddb46
--- /dev/null
+++ b/selene.toml
@@ -0,0 +1 @@
+std = "roblox"
diff --git a/src/Atom/Atom.lua b/src/Atom/Atom.lua
new file mode 100644
index 0000000..132b2aa
--- /dev/null
+++ b/src/Atom/Atom.lua
@@ -0,0 +1,12 @@
+local RunService = game:GetService("RunService")
+
+if RunService:IsServer() then
+ return require(script.AtomServer_OOP)
+else
+ local AtomServer = script:FindFirstChild("AtomServer_OOP")
+ if AtomServer and RunService:IsRunning() then
+ AtomServer:Destroy()
+ end
+
+ return require(script.AtomClient_OOP)
+end
\ No newline at end of file
diff --git a/src/Atom/AtomModuleContents/AtomClient_OOP.lua b/src/Atom/AtomModuleContents/AtomClient_OOP.lua
new file mode 100644
index 0000000..043219f
--- /dev/null
+++ b/src/Atom/AtomModuleContents/AtomClient_OOP.lua
@@ -0,0 +1,213 @@
+--!strict
+--!native
+--[[
+Rewrite of my 2019-2021 framework.
+~31/10/2023 crazyattaker1
+
+Tools Used in Atom Development: VSCode Insider Edition, Argon Code Sync.
+
+]]
+
+-- Initialize the Module Script table to allow external access
+-- to the API and making a reference to where everything SHOULD be stored.
+local AtomMain = {}
+local AtomRoot = script.Parent.Parent
+
+local started = false
+local startComplete = false
+
+-- Make Types for tables and arrays as they don't officially have types.
+type tab = { [string] : string | boolean | Instance }
+type array = { [number] : typ }
+
+-- Set Important Directories
+local Controllers = AtomRoot.Controllers
+local Components = AtomRoot.Components
+local Packages = AtomRoot:WaitForChild("Packages")
+
+-- Overwrite Functions
+--local ModuleLoader = require(Packages.ModuleLoader) -- Needed for custom require.
+--[[local function require(Directory:Instance, ScriptName:string)
+ return ModuleLoader.new(Directory, ScriptName)
+end ]]
+local function wait(seconds:number)
+ return task.wait(seconds)
+end
+local function Wait(duration:number)
+ return task.wait(duration)
+end
+local function SubTick()
+ return tick() / 2
+end
+
+-- Load all the Packages into Memory.
+--[[
+local BSON = require(Packages, "BSON")
+local GoodSignal = require(Packages, "GoodSignal")
+local Janitor = require(Packages, "Janitor")
+local PartCache = require(Packages, "PartCache")
+local Promise = require(Packages, "Promise")
+local Proto = require(Packages, "proto")
+local ProfileService = require(Packages, "ProfileService")
+local Sourceesque = require(Packages, "Sourceesque")
+local Warp = require(Packages, "Warp") ]]
+
+local GoodSignal = require(Packages.GoodSignal)
+local Janitor = require(Packages.Janitor)
+local Promise = require(Packages.Promise)
+local Switch, case, default = unpack(require(Packages.Switch))
+
+function serialize(Buffer: buffer, offset: number, DataType: string, Data)
+ local dataType = string.lower(DataType)
+
+ Switch(Data)({
+ case("number")(function()
+ buffer.writef64(Buffer, offset * math.random(1, 34), Data)
+ end),
+
+ case("string")(function()
+ if #Data <= 1024 then -- each character takes 1 byte
+ buffer.writestring(Buffer, 0, Data)
+ else
+ return warn("Data is too large for the buffer.")
+ end
+ end),
+
+ case("table")(function()
+ for i, v in ipairs(Data) do
+ serialize(Buffer, offset, DataType, v) -- recursively serialize each element in the table
+ end
+ end),
+
+ default()(function()
+ return warn("Unsupported Data Type.")
+ end),
+ })
+end
+
+-- Make references for Repositories.
+local ReplicatedStorage = game:GetService("ReplicatedStorage")
+
+local Remotes = AtomRoot.AtomRemotes
+
+local onCompletedSignal = Instance.new("BindableEvent", Remotes.BindableEvents)
+onCompletedSignal.Name = "onCompleted"
+local ErrorSignal = GoodSignal.new()
+
+-- DRY Signal Handler.
+ErrorSignal:Connect(function(message)
+ error(message, 2)
+end)
+
+--[[
+Internal clean function.
+
+Run the function Clean()
+
+This function can be used anywhere internally and should
+be used like the following:
+function YourFunction(YourFunctionsParmaters)
+ local YourFunctionsJanitor = Janitor.new()
+ -- Your Function's Code
+ YourFunctionsJanitor:Add(YourFunctionsInstance)
+ Clean(YourFunctionsJanitor)
+end ]]
+
+function Clean(WorkingJanitor)
+ WorkingJanitor:Cleanup()
+ print("Memory Usage: "..collectgarbage('count').."kb")
+ print("Memory Usage: "..collectgarbage('count') * 1024, "B")
+ return "Cleaned"
+end
+
+--[[
+Initialize the framework.
+
+Require the ModuleScript and run this function ONCE
+to set up the framework for everything else.
+
+This function is located in the customizable BootStrapper
+parented under this ModuleScript and should be used like
+the following:
+local Origin = require(script.Parent)
+Origin:Init()
+
+]]
+function AtomMain.Start()
+ if started then return Promise.reject("Atom has already started.") end
+ if _VERSION ~= "Luau" then ErrorSignal:Fire("Running on an External Lua Runtime.") return end
+
+ local StartTick = tick()
+ local StartSubTick = SubTick()
+
+ started = true
+
+ return Promise.new(function(resolve)
+ -- Create a Janitor to clean unneeded files post setup.
+ local InitJanitor = Janitor.new()
+
+ local controllerInitPromises = {}
+
+ for _, Controller in ipairs(Controllers:GetDescendants()) do
+ if Controller:IsA("ModuleScript") and Controller.Name:match("Controller$") then
+ table.insert(
+ controllerInitPromises,
+ Promise.new(function(r)
+ debug.setmemorycategory(Controller.Name)
+ local controllertouse = require(Controllers, Controller.Name)
+ controllertouse.new()
+ controllertouse:Init()
+ r()
+ end)
+ )
+ end
+ end
+
+ -- Cleaning up anything left from the Auto Installer.
+ print(Clean(InitJanitor))
+ resolve(Promise.all(controllerInitPromises))
+ end):andThen(function()
+ local controllersStartPromises = {}
+
+ for _, controller in ipairs(Controllers:GetDescendants()) do
+ if controller:IsA("ModuleScript") and controller.Name:match("Controller$") then
+ table.insert(
+ controllersStartPromises,
+ Promise.new(function(r)
+ debug.setmemorycategory(controller.Name)
+ local controllertouse = require(Controllers, controller.Name)
+ controllertouse.new()
+ controllertouse:Start()
+ r()
+ end)
+ )
+ end
+ end
+
+ startComplete = true
+ local EndTick = tick()
+ local EndSubTick = SubTick()
+ onCompletedSignal:Fire("Atom has started succesfully.")
+ local InitTick = tostring(EndTick - StartTick)
+ local InitSubTick = tostring(EndSubTick - StartSubTick)
+ print("Atom Started with a tick of "..InitTick.." and a sub tick of "..InitSubTick..".")
+ end)
+end
+
+--[[function AtomMain.GetController(ControllerName:string)
+ for i, v in ipairs(Controllers:GetDescendants()) do
+ if v:IsA("ModuleScript") and v.Name == ControllerName then
+ return require(Controllers, ControllerName)
+ else
+ continue
+ end
+ end
+end ]]
+
+local Core = script.Parent.Core.Client
+
+return {
+ versiondetails = { major = 0, minor = 1, isrelease = false },
+ AtomRoot = AtomRoot,
+ Main = AtomMain
+}
\ No newline at end of file
diff --git a/src/Atom/AtomModuleContents/AtomServer_OOP.lua b/src/Atom/AtomModuleContents/AtomServer_OOP.lua
new file mode 100644
index 0000000..df44b89
--- /dev/null
+++ b/src/Atom/AtomModuleContents/AtomServer_OOP.lua
@@ -0,0 +1,261 @@
+--!native
+--!strict
+--[[
+
+Rewrite of my 2019-2021 framework.
+~31/10/2023 crazyattaker1
+
+V0.1
+Started Development.
+
+V0.2
+Moved some of the code in the main script into modular code for readability.
+Added SubTicks.
+Temporarily removed my ModuleLoader as it broke intellisense.
+Added the serializer.
+
+V0.4
+Implemented the AtomMain.GetService() function,
+Implemented the AtomMain.CreateRemoteEvent() function,
+Implemented the AtomMain.CreateUnreliableEvent() function,
+Implented the Atom.GetSignal() function,
+
+V0.5
+Moved the serializer to a seperate script for modularity and added a copy of it's requirements to it.
+Fixed the cylical dependency bug in the Module Loader.
+
+Tools Used in Atom Development: VSCode Insider Edition, Argon Code Sync.
+
+]]
+
+-- Initialize the Module Script table to allow external access
+-- to the API and making a reference to where everything SHOULD be stored.
+local AtomMain = {}
+local AtomRoot = script.Parent.Parent
+AtomRoot.Parent = game:GetService("ReplicatedStorage")
+
+local started: boolean = false
+local startComplete: boolean = false
+
+-- Set Important Directories
+local Services = AtomRoot.Services -- Needed for custom require and it will be needed further in the script.
+local Controllers = AtomRoot.Controllers
+local Components = AtomRoot.Components
+local Packages = AtomRoot:WaitForChild("Packages")
+
+local Utility = Packages.Utility
+
+-- Overwrite Functions
+local ModuleLoader = require(script.Parent.Utils.ModuleLoader) -- Needed for custom require.
+--[[local function require(Directory:Instance, ScriptName:string)
+ return ModuleLoader:require(Directory, ScriptName)
+end ]]
+
+function SubTick()
+ return tick() / 2
+end
+
+--[[ Currently unused as it broke intellisense. [[
+local BSON = require(Packages, "BSON")
+local ByteNet = require(Packages, "ByteNet")
+local GoodSignal = require(Packages, "GoodSignal")
+local Janitor = require(Packages, "Janitor")
+local Promise = require(Packages, "Promise")
+local Proto = require(Packages, "proto")
+local ProfileService = require(Packages, "ProfileService")
+local Sourceesque = require(Packages, "Sourceesque")
+local Warp = require(Packages, "Warp")
+local RestrictRead = require(Utility, "restrictRead")
+local Switch, case, default = unpack(require(Packages, "Switch")) ]]
+
+local BSON = require(Packages.BSON)
+local ByteNet = require(Packages.ByteNet)
+local GoodSignal = require(Packages.GoodSignal)
+local Janitor = require(Packages.Janitor)
+local Promise = require(Packages.Promise)
+local Proto = require(Packages.proto)
+local ProfileService = require(Packages.ProfileService)
+local Sourceesque = require(Packages.Sourceesque)
+local Warp = require(Packages.Warp)
+local Switch, case, default = unpack(require(Packages.Switch))
+
+-- Make references for Repositories.
+local ReplicatedStorage = game:GetService("ReplicatedStorage")
+local ServerStorage = game:GetService("ServerStorage")
+
+local Remotes = AtomRoot.AtomRemotes
+
+local onCompletedSignal = Instance.new("BindableEvent", Remotes.BindableEvents)
+onCompletedSignal.Name = "onCompleted"
+local ErrorSignal = GoodSignal.new()
+
+-- DRY Signal Handler.
+ErrorSignal:Connect(function(message)
+ error(message, 2)
+end)
+
+--[[
+Internal clean function.
+
+Run the function Clean()
+
+This function can be used anywhere internally and should
+be used like the following:
+function YourFunction(YourFunctionsParmaters)
+ local YourFunctionsJanitor = Janitor.new()
+ -- Your Function's Code
+ YourFunctionsJanitor:Add(YourFunctionsInstance)
+ Clean(YourFunctionsJanitor)
+end
+
+]]
+
+function Clean(WorkingJanitor)
+ WorkingJanitor:Cleanup()
+ return "Cleaned."
+end
+
+--[[
+Start the framework.
+
+Require the ModuleScript and run this function ONCE
+to set up the framework for everything else.
+
+This function is located in the customizable BootStrapper
+parented under this ModuleScript and should be used like
+the following:
+local Atom = require(script.Parent)
+Atom.Start()
+
+]]
+function AtomMain.Start()
+ if started then return Promise.reject("Atom has already started.") end
+ if _VERSION ~= "Luau" then ErrorSignal:Fire("Running on an External Lua Runtime.") return end
+
+ local StartTick = tick()
+ local StartSubTick = SubTick()
+
+ started = true
+
+ return Promise.new(function(resolve)
+ -- Create a Janitor to clean unneeded files post setup.
+ local InitJanitor = Janitor.new()
+
+ Remotes.Parent = ReplicatedStorage
+ Services.Parent = ServerStorage
+ Controllers.Parent = ReplicatedStorage
+ Components.Parent = ReplicatedStorage
+
+ -- Auto Installer.
+ -- Reads through the Atom Root Directory to find files before checking the
+ -- Cleanable attribute. If the attribute is true the file will be cleaned and if it's
+ -- false then it will be left alone.
+
+ local MaxFolderItterations = 4
+ local CurrentFolderItterations = 0
+ for i, v in ipairs(AtomRoot:GetChildren()) do
+ if CurrentFolderItterations >= MaxFolderItterations then
+ break
+ end
+
+ if CurrentFolderItterations < MaxFolderItterations then
+ CurrentFolderItterations = CurrentFolderItterations + 1
+ print("Currently on the " .. CurrentFolderItterations .. " itteration.")
+ if v:IsA("Folder") then -- If it's a Folder, continue.
+ if v:GetAttribute("Cleanable") ~= false then
+ -- Reads through the directory to find any files to be moved before
+ -- preparing it to be cleaned.
+ for _, scriptObject in pairs(v:GetChildren()) do
+ InitJanitor:Add(v)
+ end
+ end
+ end
+ end
+ end
+
+ local servicesInitPromises = {}
+
+ for _, service in ipairs(Services:GetDescendants()) do
+ if service:IsA("ModuleScript") and service.Name:match("Service$") then
+ table.insert(
+ servicesInitPromises,
+ Promise.new(function(r)
+ debug.setmemorycategory(service.Name)
+ local servicetouse = require(Services:WaitForChild(service.Name))
+ local serviceClass = servicetouse.new()
+ serviceClass:Init()
+ task.wait()
+ serviceClass:Start()
+ r()
+ end)
+ )
+ end
+ end
+
+ -- Cleaning up anything left from the Auto Installer.
+ print(Clean(InitJanitor))
+ resolve(Promise.all(servicesInitPromises))
+ end):andThen(function()
+ --[[local servicesStartPromises = {}
+
+ for _, service in ipairs(Services:GetDescendants()) do
+ if service:IsA("ModuleScript") and service.Name:match("Service$") then
+ table.insert(
+ servicesStartPromises,
+ Promise.new(function(r)
+ debug.setmemorycategory(service.Name)
+ local servicetouse = require(Services:WaitForChild(service.Name))
+ local serviceClass = servicetouse.new()
+ serviceClass:Start()
+ r()
+ end)
+ )
+ end
+ end ]]
+
+ startComplete = true
+ local EndTick = tick()
+ local EndSubTick = SubTick()
+ onCompletedSignal:Fire("Atom has started succesfully.")
+ local InitTick = tostring(EndTick - StartTick)
+ local InitSubTick = tostring(EndSubTick - StartSubTick)
+ print("Atom Started with a tick of " .. InitTick .. " and a sub tick of " .. InitSubTick .. ".")
+ end)
+end
+
+function AtomMain.GetService(ServiceName: string)
+ for i, v in ipairs(Services:GetChildren()) do
+ if v.ClassName ~= "ModuleScript" and v.Name ~= ServiceName then continue end
+ return require(Services:WaitForChild(ServiceName))
+ end
+end
+
+function AtomMain.CreateRemoteEvent(RemoteName: string)
+ local RemoteEvent = Instance.new("RemoteEvent")
+ RemoteEvent.Parent = Remotes.RemoteEvents
+ RemoteEvent.Name = RemoteName
+end
+
+function AtomMain.CreateUnreliableRemoteEvent(RemoteName: string)
+ local UnreliableRemoteEvent = Instance.new("UnreliableRemoteEvent")
+ UnreliableRemoteEvent.Parent = Remotes.UnreliableRemoteEvents
+ UnreliableRemoteEvent.Name = RemoteName
+end
+
+function AtomMain.GetSignal(SignalName: string, SignalType: string)
+ return Remotes:WaitForChild(SignalType.."s"):FindFirstChild(SignalName)
+end
+
+local Core = script.Parent.Core
+
+return {
+ versiondetails = { major = 0, minor = 5, isrelease = false },
+ AtomRoot = AtomRoot,
+ Core = AtomRoot.Atom.Core,
+ Util = AtomRoot.Atom.Utils,
+ Main = AtomMain,
+ Badges = require(Core.Badges),
+ DataStores = require(Core.DataStore).DataStores,
+ MemoryStoreOperations = require(Core.MemoryStoreOperations),
+ Serializer = require(Core.Serializer)
+}
diff --git a/src/Atom/AtomModuleContents/ClientBootstrapper.client.lua b/src/Atom/AtomModuleContents/ClientBootstrapper.client.lua
new file mode 100644
index 0000000..4d3ee16
--- /dev/null
+++ b/src/Atom/AtomModuleContents/ClientBootstrapper.client.lua
@@ -0,0 +1,7 @@
+--!strict
+local Atom = require(script.Parent)
+
+local Main = Atom.Main
+
+Main.Start()
+print(Atom.versiondetails)
\ No newline at end of file
diff --git a/src/Atom/AtomModuleContents/Core/Badges.lua b/src/Atom/AtomModuleContents/Core/Badges.lua
new file mode 100644
index 0000000..db24b59
--- /dev/null
+++ b/src/Atom/AtomModuleContents/Core/Badges.lua
@@ -0,0 +1,25 @@
+--!native
+--!strict
+local Badges = {}
+
+-- Badge Service.
+function Badges:GetBadgeService()
+ return game:GetService("BadgeService")
+end
+
+function Badges:AwardBadge(player:Player, BadgeID:number)
+ local BadgeService = Badges:GetBadgeService()
+ local DoesThePlayerHaveTheBadge = BadgeService:UserHasBadgeAsync(player.UserId, BadgeID)
+
+ local success, errormessage = pcall(function()
+ if DoesThePlayerHaveTheBadge ~= true then
+ BadgeService:AwardBadge(player.UserId, BadgeID)
+ end
+ end)
+
+ if not success then
+ player:Kick("Error with BadgeService. "..errormessage)
+ end
+end
+
+return Badges
diff --git a/src/Atom/AtomModuleContents/Core/MemoryStoreOperations.lua b/src/Atom/AtomModuleContents/Core/MemoryStoreOperations.lua
new file mode 100644
index 0000000..f2e6d65
--- /dev/null
+++ b/src/Atom/AtomModuleContents/Core/MemoryStoreOperations.lua
@@ -0,0 +1,72 @@
+--!native
+--!strict
+local MemoryStoreOperations = {}
+
+-- Memory Store Service.
+-- Functions.
+function MemoryStoreOperations:GetMemoryStoreService()
+ return game:GetService("MemoryStoreService")
+end
+
+function MemoryStoreOperations:CreateMemoryStoreQueue(QueueName:string)
+ return game:GetService("MemoryStoreService"):GetQueue(QueueName)
+end
+
+function MemoryStoreOperations:GetSortedMap(name:string)
+ return game:GetService("MemoryStoreService"):GetSortedMap(name)
+end
+
+function MemoryStoreOperations:AddToMemoryStoreQueue(Queue:MemoryStoreQueue, Value:any, Expiration:number, Priority:number)
+ Queue:AddAsync(Value, Expiration, Priority)
+end
+
+function MemoryStoreOperations:AddToSortedMap(SortedMap:MemoryStoreSortedMap, Key:string, Value:any, Expiration:number)
+ SortedMap:SetAsync(Key, Value, Expiration)
+end
+
+function MemoryStoreOperations:GetMemoryIdentifier(Queue:MemoryStoreQueue, count:number, allOrNothing:boolean, waitTimeout:number)
+ local results, identifier = Queue:ReadAsync(count, allOrNothing, waitTimeout)
+ return identifier
+end
+
+function MemoryStoreOperations:RemoveFromMemoryStoreQueue(Queue:MemoryStoreQueue, Identifier:string)
+ local success, errormessage = pcall(function()
+ Queue:RemoveAsync(Identifier)
+ end)
+ if not success then
+ warn("Error removing from MemoryStoreQueue. "..errormessage)
+ end
+end
+
+function MemoryStoreOperations:ReadFromMemoryStoreQueue(Queue:MemoryStoreQueue, count:number, allOrNothing:boolean, waitTimeout:number)
+ local results = nil
+ local success, errormessage = pcall(function()
+ local results, identifier = Queue:ReadAsync(count, allOrNothing, waitTimeout)
+ end)
+ if success then
+ return results
+ elseif not success then
+ return "Error reading MemoryStoreQueue. "..errormessage
+ else
+ return "Error with function. "..errormessage
+ end
+end
+
+function MemoryStoreOperations:ReadFromSortedMap(SortedMap:MemoryStoreSortedMap, Key:string)
+ local success, errormessage = pcall(function()
+ SortedMap:GetAsync(Key)
+ end)
+ if success then
+ return SortedMap:GetAsync(Key)
+ elseif not success then
+ warn("Error getting Value! "..errormessage)
+ else
+ warn("Error with function! "..errormessage)
+ end
+end
+
+function MemoryStoreOperations:UpdateSortedMapValue(SortedMap:MemoryStoreSortedMap, Key, transformFunction, Expiration:number)
+ SortedMap:UpdateAsync(Key, transformFunction, Expiration)
+end
+
+return MemoryStoreOperations
\ No newline at end of file
diff --git a/src/Atom/AtomModuleContents/Core/Serializer.lua b/src/Atom/AtomModuleContents/Core/Serializer.lua
new file mode 100644
index 0000000..c66dd94
--- /dev/null
+++ b/src/Atom/AtomModuleContents/Core/Serializer.lua
@@ -0,0 +1,58 @@
+local Switch, case, default = unpack(require(script.Switch))
+
+local Serializer = {}
+
+function Serializer.serialize(Buffer: buffer, offset: number, DataType: string, Data)
+ local dataType = string.lower(DataType)
+
+ Switch(Data)({
+ case("number")(function()
+ buffer.writef64(Buffer, offset * math.random(1, 34), Data)
+ end),
+
+ case("string")(function()
+ if #Data <= 1024 then -- each character takes 1 byte
+ buffer.writestring(Buffer, 0, Data)
+ else
+ return warn("Data is too large for the buffer.")
+ end
+ end),
+
+ case("table")(function()
+ for i, v in ipairs(Data) do
+ Serializer.serialize(Buffer, offset, DataType, v) -- recursively serialize each element in the table
+ end
+ end),
+
+ default()(function()
+ return warn("Unsupported Data Type.")
+ end),
+ })
+end
+
+function Serializer.deserialize(Buffer, offset, DataType)
+ local dataType = string.lower(DataType)
+
+ if dataType == "number" then
+ -- Assuming buffer.readf64 reads a 64-bit float
+ local data = buffer.readf64(Buffer, offset)
+ return data
+ elseif dataType == "string" then
+ -- Assuming buffer.readstring reads a string from the buffer
+ local data = buffer.readstring(Buffer, offset, 0)
+ return data
+ --[[elseif dataType == "table" then
+ local result = {}
+ -- Assuming you know the table structure (e.g., fixed size or dynamic)
+ -- You'll need to adapt this part based on your specific use case
+ for i = 1, numElements do
+ local element = deserialize(Buffer, offset, DataType)
+ table.insert(result, element)
+ end
+ return result ]]
+ else
+ print("Unsupported Data Type.")
+ end
+end
+
+return Serializer
diff --git a/src/Atom/AtomModuleContents/Core/Switch.lua b/src/Atom/AtomModuleContents/Core/Switch.lua
new file mode 100644
index 0000000..93e8dea
--- /dev/null
+++ b/src/Atom/AtomModuleContents/Core/Switch.lua
@@ -0,0 +1,104 @@
+local bypassIndex = math.huge -- As a heads up, NEVER set your case value to this. It will break things!
+
+local self; self = {
+ [1] = function(condition) -- Switch
+ -- The first thing we want to do is get that condition, and NOT call the case function, so we will just
+ -- return another function with the functionality of a case
+
+ return function(cases)
+ for index, case in ipairs(cases) do
+ local conditionResult, caseFunction = case(condition)
+
+ if typeof(conditionResult) == "function" then
+ -- If conditionResult is a function, we can assume we just came across a default statement
+
+ if index ~= #cases then
+ error("Attempted to use a default statement in an index that was not the last")
+
+ else
+ conditionResult()
+ break
+ end
+
+ elseif typeof(conditionResult) == "boolean" then
+ -- If conditionResult is a boolean, we can assume we just came across a case statement
+
+ if conditionResult then
+ if caseFunction then
+ -- If the case has its own defined function, immediately call it
+
+ caseFunction()
+
+ else
+ -- Otherwise, let's look for the next case attached to a function
+
+ while true do
+ index += 1
+
+ if cases[index] == self[3] then
+ warn("Case " .. condition .. " does not ever break, and ends in a default statement. Did you forget to include a function somewhere?")
+ break
+ end
+
+ local _, newCaseFunction = cases[index](bypassIndex)
+
+ if newCaseFunction then
+ newCaseFunction()
+ break
+
+ elseif index > #cases then
+ warn("Case " .. condition .. " does not ever break. Did you forget to include a function somewhere?")
+ break
+ end
+ end
+ end
+
+ break
+ end
+
+ else
+ -- We should expect cases to return a boolean userdata value, and a function userdata value for default statements
+ -- Anything else is unacceptable because its not a compatible statement
+
+ error("Attempted to use a non-valid statement in the switch statement")
+ end
+
+ end
+ end
+ end,
+
+ [2] = function(requiredValue) -- Case
+ -- The first thing we want is to get the checked comparison
+ -- Of course, we don't want to run the case function, so let's
+ -- return a parent function
+
+ return function(caseFunction)
+ return function(conditionValue)
+ -- In here, we check if the condition meets the case required value
+ -- If so, the switch statement will recognize this by recieving a
+ -- true value, and this finding the first occuring attached function.
+ -- If not, the switch statement will recieve a false value and ignore
+ -- the case.
+
+ if conditionValue == bypassIndex or conditionValue == requiredValue then
+ return true, caseFunction
+ else
+ return false, nil
+ end
+ end
+ end
+ end,
+
+ [3] = function() -- Default
+ -- This works almost exactly like a case, except we do not check a condition
+ -- This is similar to an "else" statement in lua/luau
+
+ return function(defaultFunction)
+ return function()
+ return defaultFunction
+ end
+ end
+ end,
+}
+
+return self
\ No newline at end of file
diff --git a/src/Atom/AtomModuleContents/ServerBootstrapper.server.lua b/src/Atom/AtomModuleContents/ServerBootstrapper.server.lua
new file mode 100644
index 0000000..4d3ee16
--- /dev/null
+++ b/src/Atom/AtomModuleContents/ServerBootstrapper.server.lua
@@ -0,0 +1,7 @@
+--!strict
+local Atom = require(script.Parent)
+
+local Main = Atom.Main
+
+Main.Start()
+print(Atom.versiondetails)
\ No newline at end of file
diff --git a/src/Atom/AtomModuleContents/Utils/ModuleLoader.lua b/src/Atom/AtomModuleContents/Utils/ModuleLoader.lua
new file mode 100644
index 0000000..c0b30bc
--- /dev/null
+++ b/src/Atom/AtomModuleContents/Utils/ModuleLoader.lua
@@ -0,0 +1,19 @@
+--!strict
+-- Written by crazyattaker1. 04/04/2024. Loads Atom Modules.
+local ModuleLoader = {}
+ModuleLoader.__index = ModuleLoader
+
+function ModuleLoader:require(Directory, ScriptName:string)
+ local Modules = Directory:GetChildren()
+
+ for i, v in ipairs(Modules) do
+ if v:IsA("ModuleScript") and v.Name == ScriptName then
+ print("Module Loader: "..i..". Requiring "..v:GetFullName())
+ return require(v)
+ end
+ end
+
+ return nil
+end
+
+return ModuleLoader
\ No newline at end of file
diff --git a/src/Atom/AtomModuleContents/Utils/Signal.lua b/src/Atom/AtomModuleContents/Utils/Signal.lua
new file mode 100644
index 0000000..e3a1129
--- /dev/null
+++ b/src/Atom/AtomModuleContents/Utils/Signal.lua
@@ -0,0 +1,180 @@
+--------------------------------------------------------------------------------
+-- Batched Yield-Safe Signal Implementation --
+-- This is a Signal class which has effectively identical behavior to a --
+-- normal RBXScriptSignal, with the only difference being a couple extra --
+-- stack frames at the bottom of the stack trace when an error is thrown. --
+-- This implementation caches runner coroutines, so the ability to yield in --
+-- the signal handlers comes at minimal extra cost over a naive signal --
+-- implementation that either always or never spawns a thread. --
+-- --
+-- API: --
+-- local Signal = require(THIS MODULE) --
+-- local sig = Signal.new() --
+-- local connection = sig:Connect(function(arg1, arg2, ...) ... end) --
+-- sig:Fire(arg1, arg2, ...) --
+-- connection:Disconnect() --
+-- sig:DisconnectAll() --
+-- local arg1, arg2, ... = sig:Wait() --
+-- --
+-- Licence: --
+-- Licenced under the MIT licence. --
+-- --
+-- Authors: --
+-- stravant - July 31st, 2021 - Created the file. --
+--------------------------------------------------------------------------------
+
+-- The currently idle thread to run the next handler on
+local freeRunnerThread = nil
+
+-- Function which acquires the currently idle handler runner thread, runs the
+-- function fn on it, and then releases the thread, returning it to being the
+-- currently idle one.
+-- If there was a currently idle runner thread already, that's okay, that old
+-- one will just get thrown and eventually GCed.
+local function acquireRunnerThreadAndCallEventHandler(fn, ...)
+ local acquiredRunnerThread = freeRunnerThread
+ freeRunnerThread = nil
+ fn(...)
+ -- The handler finished running, this runner thread is free again.
+ freeRunnerThread = acquiredRunnerThread
+end
+
+-- Coroutine runner that we create coroutines of. The coroutine can be
+-- repeatedly resumed with functions to run followed by the argument to run
+-- them with.
+local function runEventHandlerInFreeThread()
+ -- Note: We cannot use the initial set of arguments passed to
+ -- runEventHandlerInFreeThread for a call to the handler, because those
+ -- arguments would stay on the stack for the duration of the thread's
+ -- existence, temporarily leaking references. Without access to raw bytecode
+ -- there's no way for us to clear the "..." references from the stack.
+ while true do
+ acquireRunnerThreadAndCallEventHandler(coroutine.yield())
+ end
+end
+
+-- Connection class
+local Connection = {}
+Connection.__index = Connection
+
+function Connection.new(signal, fn)
+ return setmetatable({
+ _connected = true,
+ _signal = signal,
+ _fn = fn,
+ _next = false,
+ }, Connection)
+end
+
+function Connection:Disconnect()
+ self._connected = false
+
+ -- Unhook the node, but DON'T clear it. That way any fire calls that are
+ -- currently sitting on this node will be able to iterate forwards off of
+ -- it, but any subsequent fire calls will not hit it, and it will be GCed
+ -- when no more fire calls are sitting on it.
+ if self._signal._handlerListHead == self then
+ self._signal._handlerListHead = self._next
+ else
+ local prev = self._signal._handlerListHead
+ while prev and prev._next ~= self do
+ prev = prev._next
+ end
+ if prev then
+ prev._next = self._next
+ end
+ end
+end
+
+-- Make Connection strict
+setmetatable(Connection, {
+ __index = function(tb, key)
+ error(("Attempt to get Connection::%s (not a valid member)"):format(tostring(key)), 2)
+ end,
+ __newindex = function(tb, key, value)
+ error(("Attempt to set Connection::%s (not a valid member)"):format(tostring(key)), 2)
+ end
+})
+
+-- Signal class
+local Signal = {}
+Signal.__index = Signal
+
+function Signal.new()
+ return setmetatable({
+ _handlerListHead = false,
+ }, Signal)
+end
+
+function Signal:Connect(fn)
+ local connection = Connection.new(self, fn)
+ if self._handlerListHead then
+ connection._next = self._handlerListHead
+ self._handlerListHead = connection
+ else
+ self._handlerListHead = connection
+ end
+ return connection
+end
+
+-- Disconnect all handlers. Since we use a linked list it suffices to clear the
+-- reference to the head handler.
+function Signal:DisconnectAll()
+ self._handlerListHead = false
+end
+
+-- Signal:Fire(...) implemented by running the handler functions on the
+-- coRunnerThread, and any time the resulting thread yielded without returning
+-- to us, that means that it yielded to the Roblox scheduler and has been taken
+-- over by Roblox scheduling, meaning we have to make a new coroutine runner.
+function Signal:Fire(...)
+ local item = self._handlerListHead
+ while item do
+ if item._connected then
+ if not freeRunnerThread then
+ freeRunnerThread = coroutine.create(runEventHandlerInFreeThread)
+ -- Get the freeRunnerThread to the first yield
+ coroutine.resume(freeRunnerThread)
+ end
+ task.spawn(freeRunnerThread, item._fn, ...)
+ end
+ item = item._next
+ end
+end
+
+-- Implement Signal:Wait() in terms of a temporary connection using
+-- a Signal:Connect() which disconnects itself.
+function Signal:Wait()
+ local waitingCoroutine = coroutine.running()
+ local cn;
+ cn = self:Connect(function(...)
+ cn:Disconnect()
+ task.spawn(waitingCoroutine, ...)
+ end)
+ return coroutine.yield()
+end
+
+-- Implement Signal:Once() in terms of a connection which disconnects
+-- itself before running the handler.
+function Signal:Once(fn)
+ local cn;
+ cn = self:Connect(function(...)
+ if cn._connected then
+ cn:Disconnect()
+ end
+ fn(...)
+ end)
+ return cn
+end
+
+-- Make signal strict
+setmetatable(Signal, {
+ __index = function(tb, key)
+ error(("Attempt to get Signal::%s (not a valid member)"):format(tostring(key)), 2)
+ end,
+ __newindex = function(tb, key, value)
+ error(("Attempt to set Signal::%s (not a valid member)"):format(tostring(key)), 2)
+ end
+})
+
+return Signal
diff --git a/src/Atom/Components/RemoteEvent.lua b/src/Atom/Components/RemoteEvent.lua
new file mode 100644
index 0000000..684892e
--- /dev/null
+++ b/src/Atom/Components/RemoteEvent.lua
@@ -0,0 +1,50 @@
+local ClientRemoteEvent = {}
+local ServerRemoteEvent = {}
+ClientRemoteEvent.__index = ClientRemoteEvent
+ServerRemoteEvent.__index = ServerRemoteEvent
+
+local RunService = game:GetService("RunService")
+
+local function serialize(Buffer : buffer, offset : number, DataType:string , Data)
+ local dataType = string.lower(DataType)
+
+ if dataType == "number" then
+ buffer.writef64(Buffer, offset * math.random(1, 34), Data)
+ elseif dataType == "string" then
+ if #Data <= 1024 then -- each character takes 1 byte
+ buffer.writestring(Buffer, 0, Data)
+ else
+ return warn("Data is too large for the buffer.")
+ end
+ elseif dataType == "table" then
+ for i, v in ipairs(Data) do
+ serialize(Buffer, offset, DataType, v) -- recursively serialize each element in the table
+ end
+ else
+ return warn("Unsupported Data Type.")
+ end
+end
+
+function ServerRemoteEvent.new(RemoteName:string, Parent:Instance)
+ local ServerRemoteEventInstance = Instance.new("RemoteEvent", Parent)
+ ServerRemoteEventInstance.Name = RemoteName
+ return ServerRemoteEventInstance
+end
+
+function ClientRemoteEvent.new(RemoteName:string, Parent:Instance)
+ local ClientRemoteEventInstance = Instance.new("RemoteEvent", Parent)
+ ClientRemoteEventInstance.Name = RemoteName
+ return ClientRemoteEventInstance
+end
+
+function ServerRemoteEvent:FireClient(player:Player, Remote:RemoteEvent, Data:any)
+ Remote:FireClient(player, Data)
+end
+
+if RunService:IsServer() then
+ return ServerRemoteEvent
+elseif RunService:IsClient() then
+ return ClientRemoteEvent
+else
+ return warn("Unable to detect runtime environment.")
+end
\ No newline at end of file
diff --git a/src/Atom/Services/TemplateService.lua b/src/Atom/Services/TemplateService.lua
new file mode 100644
index 0000000..a1e66e4
--- /dev/null
+++ b/src/Atom/Services/TemplateService.lua
@@ -0,0 +1,21 @@
+local Template = {}
+Template.__index = Template
+
+function Template.new()
+ local self = {}
+ function self.Init()
+ print("Atom TemplateService initialized.")
+ end
+
+ function self.Start()
+ print("Atom TemplateService started.")
+ end
+
+ return self
+end
+
+function Template.hi()
+ print("Hi")
+end
+
+return Template