Osier-Framework - Simplified datastores and remotes for simplified projects

Hello, i think i’ve finally done enough redesigning and fine tuning to get this stable enough for a public release, so thats what i’ll be doing today.

The Osier-Framework

Information

Osier is a framework designed to be simple and small, it handles datastores automatically after initiated, includes session locking, remote utilities, live data replication, custom events and has a specific initiation order to follow. (although it supports multi-script designs with the use :WaitForClient() and the :WaitForServer() functions)


I’ll be updating this more in the meantime, this is a fairly new project so I would expect unusually big changes as I figure out the best way to do certain things. (constructive criticism is good, whether its mean or nice, just hit me with any suggestions you can think of)

Thanks for reading, You can view a quick tutorial in the README and I’ve started documenting everything with the use of github wiki.


Github Repository

codyorr4-roblox/Osier-Framework

Github Wiki
Asset

Osier-Framework - Roblox

Getting Started

Using the Server

  • add a Server Script as a child of the Server Module
-- Require the server and cache any modules needed.
local server= require(script.Parent.Parent)
local remote = server.Remote
local playerData = server.PlayerData


-- Start the player data module.
playerData:Start(
	
	--First argument is a template for new players' data.
	{	
		Coins = 0;
		Inventory = {}
	},
	
	--Second argument is a table that specifies which values can be replicated.
	{
		Coins = true;
	}
)


-- Only the server can register remotes, 
-- "true" means it is an async request (a remoteFunction) rather than a RemoteEvent.
remote:Create("Test", true) 



-- Handle a signal for a registered remote.
remote:Handle("Test", 2000, function(player,data, data2)
	-- Print received data.
	print(data);
	print(data2)
	
	
	-- Get a value from the players session data. EX: their "Coins" value.
	print(playerData:Get(player, "Coins"))
	
	
	-- Update a players session data. EX: giving the player 20 Coins.
	playerData:Update(player, "Coins", function(old) 
		return old + 20.
	end)
	
	-- Check to see if the value changed
	print(playerData:Get(player, "Coins"))

        return  "Server has finished" -- example of returning a value.
	
end)

Using the Client

  • add a Local Script as a child of the Client Module
-- Require the Client module and cache any other modules needed.
local client = require(script.Parent.Parent)
local remote = client.Remote
local playerData = client.PlayerData


-- Locally print a replicated session data value.
-- The servers playerData:Start() function has arguments for replicating saved values.
print(playerData:Get("Coins"))


-- Locally invoke a remoteFunction that was registered by the server, it will yield and return values.
print(remote:RequestAsync("Test", "Hello from the client!", "Here is a second value"))


-- Check the replicated values again and see if the change replicated.
print(playerData:Get("Coins"))
7 Likes

The architectural pattern chosen for this was kind of overkill. I would’ve preferred a monolithic approach when writing a data abstraction library, due to how data flows in games, we usually don’t have a database for different services.


Feedback on Networking:

What it is doing poorly right now is that it is assigning a singular event for each player which is bad practice.

Querying is bad! Follow the event-based architectural pattern. Use promise to chain asynchronous methods.

Other alternatives
There are a few frameworks I want to bring up for comparison reasons and I suggest you to learn from these resources :)!

Sleitnick’s Knit, where you can initiate a remotewrapper class in a client-exposed table that will instance remotes objects on run-time.

Like this:

--server
local TestService = Knit.create {
    Name = "TestService",
    Client = {
        Test = RemoteEvent.new(),
        FromClient = RemoteEvent.new(),
    },
}

TestService.Test:FireClient(player)
TestService.FromClient:Connect(function)

--client
local TestService = Knit.GetService("TestService")
print(TestService) --> Test, Client
TestService.Test:Connect(function)
TestService.FromClient:FireServer()

Tyridge77’s EasyNetwork which is a more fleshed out implementation than yours, but nonetheless shares similar traits in how to handle remotes. Though it introduces concepts such as middlewares that can log traffic for instance, and what it does widely different from you is that instead of assigning a remoteEvent to a player, it constructs remotes after the names of a function.

Like this:

--server
Network:BindEvents {
	TestFunction = function(player)
		Network:FireClient(player, ClientSayTruth, " is awesome")
	end
}

--client
Network:BindEvents {
	ClientSayTruth = function(hiddenTruth)
		print(player.Name, truth) --> Ukendio is awesome
	end
}

Network:FireServer("TestFunction")

LPGChatGuy’s server-client (outdated), one of my favourite models, where it basically enforcing that you MUST use top-level game logic to interface with arching services. It is also super super lightweight and simple. This implementation looks like a combination of the aforementioned alternatives!

local server = ServerApi.create{
	Test = function(player)
		server:Foo(player, "Frameworks are kinda awesome")
	end,

	Bar = function() 
		print("whatt?!?")
	end
} 

--client
local client = ClientApi.create{
	Foo = function(...) 
		print(...) --> Frameworks are kinda awesome 
	end
}

client:Bar()
9 Likes

This is a great framework I look forward to using it in the future.

2 Likes