Skip to content

Commit

Permalink
New pages and fixes to docs
Browse files Browse the repository at this point in the history
  • Loading branch information
LM-loleris committed Oct 12, 2024
1 parent 271511b commit dd1967e
Show file tree
Hide file tree
Showing 7 changed files with 332 additions and 46 deletions.
40 changes: 12 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,20 @@
# MAD STUDIO - ProfileStore

ProfileStore is a Roblox DataStore wrapper that streamlines auto-saving, session locking
and a few other features for the game developer. ProfileStore's source code runs on a single
ModuleScript.
ProfileStore is a Roblox DataStore wrapper that streamlines auto-saving, session locking and a few other features for the game developer. ProfileStore's source code runs on a single ModuleScript.

If you want to save time writing code for player data caching or want to prevent item
"duping" in a game with trading - this can be a helpful resource!
If you want to save time writing code for player data caching or want to prevent item "duping" in a game with trading - this can be a helpful resource!

💲💲💲 *Consider [donating R$ to the creator of ProfileStore (Click here)](https://www.roblox.com/games/103946622805308/MAD-STUDIO-Open-Source-Donations) if you find this resource helpful!*

## How does it work?

ProfileStore loads and caches data from a DataStore key on a single Roblox game server and
prevents other game servers from accessing this data too soon by establishing a session lock
and handling session lock conflicts between servers swiftly all while not using too many
DataStore and MessagingService API calls.

Data units saved by ProfileStore are called **"profiles"** which can be accessed in-game by
starting a **"session"**. During an active session you gain access to a table ([`Profile.Data`](/ProfileStore/api/#data))
which will either be saved to the DataStore on the next auto-save or when you manually end
the session.

ProfileStore is primarily **player-data-oriented** and, by design, tweaked for a common use
case where each game player would have a single profile dedicated to storing their game
progress. Session locking addresses the issue of data access from more than one game server
(which can cause item "dupes" in games with trading) by keeping track of which game server
is currently caching data and gracefully switches ownership from one server to the other
without failing new session requests. ProfileStore can still be used for non-player data
storage, although ProfileStore's session locking is not ideal for quick writing from several
game servers.

ProfileStore's module functions try to resemble the Roblox API for a sense of familiarity
to Roblox developers. Methods with the `Async` keyword yield until a result is ready
(e.g. `:StartSessionAsync()`), while others do not.
ProfileStore loads and caches data from a DataStore key on a single Roblox game server and prevents other game servers from accessing this data too soon by establishing a session lock and handling session lock conflicts between servers swiftly all while not using too many DataStore and MessagingService API calls.

Data units saved by ProfileStore are called **"profiles"** which can be accessed in-game by starting a **"session"**. During an active session you gain access to a table ([`Profile.Data`](/ProfileStore/api/#data)) which will either be saved to the DataStore on the next auto-save or when you manually end the session.

ProfileStore is primarily **player-data-oriented** and, by design, tweaked for a common use case where each game player would have a single profile dedicated to storing their game progress. Session locking addresses the issue of data access from more than one game server (which can cause item "dupes" in games with trading) by keeping track of which game server is currently caching data and gracefully switches ownership from one server to the other without failing new session requests. ProfileStore can still be used for non-player data storage, although ProfileStore's session locking is not ideal for quick writing from several game servers.

ProfileStore's module functions try to resemble the Roblox API for a sense of familiarity to Roblox developers. Methods with the `Async` keyword yield until a result is ready (e.g. `:StartSessionAsync()`), while others do not.

**ProfileStore is not designed (and never will be) for in-game leaderboards or any kind of global state.**

Expand All @@ -44,4 +26,6 @@ to Roblox developers. Methods with the `Async` keyword yield until a result is r
**[ProfileStore wiki](https://madstudioroblox.github.io/ProfileStore/)**

***Get it now on:***
[Roblox library](https://create.roblox.com/store/asset/109379033046155/ProfileStore)
[Roblox library](https://create.roblox.com/store/asset/109379033046155/ProfileStore)

If you need help integrating ProfileStore into your project, [join the discussion on the Roblox forums (Click here)](https://devforum.roblox.com/t/profilestore/3190543).
2 changes: 2 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ The second optional argument to `ProfileStore:StartSessionAsync()` is a table wi
the profile session is still needed. If the profile is no longer needed, the `Cancel` function should return `true`.
The `Cancel` argument would be useful in rare cases where the DataStores are unresponsive and a player leaves
before a session was started allowing ProfileStore to stop making additional requests to the DataStore.
Using the `Cancel` argument also disables the default ProfileStore session start timeout as the developer
would decide when the profile is no longer needed.
- **`Steal`** - (e.g. `{Steal = true}`) If set to `true`, doesn't let an active session make final changes to `Profile.Data`
and immediately starts a session on the server calling `ProfileStore:StartSessionAsync()` with this argument.
**DO NOT USE THIS ARGUMENT FOR LOADING PLAYER DATA NORMALLY** - The `Steal` argument bypasses session locks which are needed for item "dupe" prevention.
Expand Down
69 changes: 69 additions & 0 deletions docs/datause/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
ProfileStore uses Roblox [DataStores](https://create.roblox.com/docs/cloud-services/data-stores) to store profile data.
Roblox game servers have a limit on how many DataStore API requests can be made in a certain amount of time.
You can find official information on [Roblox DataStore limits by clicking here](https://create.roblox.com/docs/cloud-services/data-stores/error-codes-and-limits#server-limits).
ProfileStore also uses Roblox [MessagingService](https://create.roblox.com/docs/reference/engine/classes/MessagingService#summary) to resolve session conflicts between multiple servers faster - Find information on [Roblox MessagingService limits by clicking here](https://create.roblox.com/docs/reference/engine/classes/MessagingService#summary).

*(During DataStore outages after a DataStore request results in an error ProfileStore may repeat requests faster or slower [using exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff))*

***Certain operations you may perform using ProfileStore will use up a certain amount of Roblox API requests:***

## On startup in Studio

- Uses 1 [`:SetAsync()`](https://create.roblox.com/docs/reference/engine/classes/GlobalDataStore#SetAsync) call to check for DataStore API access.

## [`:StartSessionAsync()`](/ProfileStore/api/#startsessionasync)

**Until a session is started:**

- Usually uses 1 [`:UpdateAsync()`](https://create.roblox.com/docs/reference/engine/classes/GlobalDataStore#UpdateAsync) call.
- If another server currently has a session started for the same profile, uses 1 [`:UpdateAsync()`](https://create.roblox.com/docs/reference/engine/classes/GlobalDataStore#UpdateAsync) call
and 1 [:PublishAsync()](https://create.roblox.com/docs/reference/engine/classes/MessagingService#PublishAsync) call,
then 5 seconds later (`FIRST_LOAD_REPEAT`) will perform 1 [`:UpdateAsync()`](https://create.roblox.com/docs/reference/engine/classes/GlobalDataStore#UpdateAsync) call and 1
[:PublishAsync()](https://create.roblox.com/docs/reference/engine/classes/MessagingService#PublishAsync) call. While the session conflict is not resolved,
1 [`:UpdateAsync()`](https://create.roblox.com/docs/reference/engine/classes/GlobalDataStore#UpdateAsync) call and 1 [:PublishAsync()](https://create.roblox.com/docs/reference/engine/classes/MessagingService#PublishAsync)
call will be repeated in 10 second intervals (`LOAD_REPEAT_PERIOD`) for the next 40 seconds (`SESSION_STEAL`) until finally a session will be stolen (1 [`:UpdateAsync()`](https://create.roblox.com/docs/reference/engine/classes/GlobalDataStore#UpdateAsync) call). Expect all of these calls to happen when players rejoin your game immediately after experiencing a server crash.

**After a session is started and until the session ends:**

- Uses 1 [`:UpdateAsync()`](https://create.roblox.com/docs/reference/engine/classes/GlobalDataStore#UpdateAsync) call in 300 second intervals (`AUTO_SAVE_PERIOD`).
When [`:Save()`](/ProfileStore/api/#save) is used, the 300 second timer will reset for this repeating call. This is the auto-save call.
- Subscribes to 1 [`MessagingService`](https://create.roblox.com/docs/reference/engine/classes/MessagingService) topic
(1 [`:SubscribeAsync()`](https://create.roblox.com/docs/reference/engine/classes/MessagingService#SubscribeAsync) call) -
this topic subscription will be ended when the profile session ends.

**After [`:EndSession()`](/ProfileStore/api/#endsession) is called OR another server starts a session for the same profile:**

- Usually uses 1 [`:UpdateAsync()`](https://create.roblox.com/docs/reference/engine/classes/GlobalDataStore#UpdateAsync) call.
- In rare cases will use 2 [`:UpdateAsync()`](https://create.roblox.com/docs/reference/engine/classes/GlobalDataStore#UpdateAsync) calls in quick succession
(1'st call - external session request detected; 2'nd call - locally broadcasting a final save and ending the session) when resolving a session conflict
if a [`MessagingService`](https://create.roblox.com/docs/reference/engine/classes/MessagingService) message asking for a session end was not received on time.

## [`:MessageAsync()`](/ProfileStore/api/#startsessionasync)

- Uses 1 [`:UpdateAsync()`](https://create.roblox.com/docs/reference/engine/classes/GlobalDataStore#UpdateAsync) call and 1
[:PublishAsync()](https://create.roblox.com/docs/reference/engine/classes/MessagingService#PublishAsync) call.
- If there's a server that currently has a session started for the targeted profile, 1 [`:UpdateAsync()`](https://create.roblox.com/docs/reference/engine/classes/GlobalDataStore#UpdateAsync) call
will be used on that server regardless of whether [`:MessageAsync()`](/ProfileStore/api/#startsessionasync) was called on the same server. The 300 second auto-save interval timer would also be reset in this scenario.
ProfileStore does not assume developer code would immediately process a message sent by [`:MessageAsync()`](/ProfileStore/api/#startsessionasync) on the same server at all times, so
instant storage of the message to the DataStore is prioritized to ensure data persistence.

## [`:GetAsync()`](/ProfileStore/api/#getasync)

- Uses 1 [`GlobalDataStore:GetAsync()`](https://create.roblox.com/docs/reference/engine/classes/GlobalDataStore#GetAsync) call.

## [`:RemoveAsync()`](/ProfileStore/api/#removeasync)

- Uses 1 [`GlobalDataStore:RemoveAsync()`](https://create.roblox.com/docs/reference/engine/classes/GlobalDataStore#RemoveAsync) call.

## [`VersionQuery:NextAsync()`](/ProfileStore/api/#versionquery)

- May use 1 [`:ListVersionsAsync()`](https://create.roblox.com/docs/reference/engine/classes/DataStore#ListVersionsAsync) call
and may use 1 [`GlobalDataStore:GetAsync()`](https://create.roblox.com/docs/reference/engine/classes/GlobalDataStore#GetAsync) call.

## [`:Save()`](/ProfileStore/api/#save)

- Uses 1 [`:UpdateAsync()`](https://create.roblox.com/docs/reference/engine/classes/GlobalDataStore#UpdateAsync) call.

## [`:SetAsync()`](/ProfileStore/api/#setasync)

- Uses 1 [`:UpdateAsync()`](https://create.roblox.com/docs/reference/engine/classes/GlobalDataStore#UpdateAsync) call.
Loading

0 comments on commit dd1967e

Please sign in to comment.