Skip to content

Commit

Permalink
Initial work
Browse files Browse the repository at this point in the history
  • Loading branch information
Vorlias committed Jan 19, 2022
0 parents commit 690234d
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 0 deletions.
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Project place file
/tests.rbxlx

# Roblox Studio lock files
/*.rbxlx.lock
/*.rbxl.lock

# roblox-ts
node_modules
tests.rbxl
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<div align="center">
<h1>MockMemoryStoreService</h1>
<h3>Emulation of Roblox's <b>MemoryStoreService</b> for offline development and testing</h3>
</div>

This is a set of modules, similar to [MockDataStoreService](https://github.com/buildthomas/MockDataStoreService) by buildthomas. This emulates the memory store service rather than using the actual service. This allows for testing it in offline places/local place files with code or frameworks that require the use of MemoryStoreService.
57 changes: 57 additions & 0 deletions lib/MockMemoryStoreService/MockMemoryStoreQueue.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
local httpService = game:GetService("HttpService")

local MockMemoryStoreQueue = {}
MockMemoryStoreQueue.__index = MockMemoryStoreQueue;

type Value = {
id: string,
value: any,
expiration: number,
priority: number,
mutex: boolean,
}

function MockMemoryStoreQueue.new(name)
return setmetatable({
__queue = {},
__refs = {}
}, MockMemoryStoreQueue)
end

function MockMemoryStoreQueue:ReadAsync(count, allOrNothing, waitTimeout)
local results = table.move(#self.__queue - count, #self.__queue, 1, #self.__queue, {})

local ref = httpService:GenerateGUID(false)
self.__refs[ref] = results

for _, value in ipairs(results) do
value.mutex = true
end
return results
end

function MockMemoryStoreQueue:RemoveAsync(ref)
local valueRef = self.__refs[ref]
if valueRef then
for _, value in ipairs(valueRef) do
local index = table.find(self.__queue, value)
table.remove(self.__queue, index)
end
end
end

function MockMemoryStoreQueue:AddAsync(value: any, expiration: number, priority: number)
table.insert(self.__queue, {
value = value,
expiration = expiration,
priority = priority,
id = httpService:GenerateGUID(false),
mutex = false
} :: Value)

table.sort(self.__queue, function(a: Value, b: Value)
return a.priority > b.priority
end)
end

return MockMemoryStoreQueue
64 changes: 64 additions & 0 deletions lib/MockMemoryStoreService/MockMemoryStoreSortedMap.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
local MockMemoryStoreSortedMap = {}
MockMemoryStoreSortedMap.__index = MockMemoryStoreSortedMap;

function MockMemoryStoreSortedMap.new(name)
return setmetatable({
__store = {}
}, MockMemoryStoreSortedMap)
end

function MockMemoryStoreSortedMap:GetAsync(key)
return self.__store[key].value
end

function MockMemoryStoreSortedMap:SetAsync(key, value, expiration)
local value = {
value = value,
expiration = tick() + expiration
}

self.__store[key] = value
return true
end

function MockMemoryStoreSortedMap:UpdateAsync(key, transformFunction, expiration)
local oldValue = self.__store[key]

local newValue = transformFunction(if oldValue then oldValue.value else nil)
if newValue ~= nil then
self:SetAsync(key, newValue, tick() + expiration)
return newValue
else
return nil
end
end

function MockMemoryStoreSortedMap:RemoveAsync(key)
self.__store[key] = nil
end

local function sortPairsAscending(valueA, valueB)
return utf8.codepoint(valueA[1]) < utf8.codepoint(valueB[1])
end

local function sortPairsDescending(valueA, valueB)
return utf8.codepoint(valueA[1]) > utf8.codepoint(valueB[1])
end

function MockMemoryStoreSortedMap:GetRangeAsync(direction, count, exclusiveLowerBound, exclusiveUpperBound)
assert(direction ~= nil)
assert(count ~= nil)

local keyPairs = {}
for key, value in pairs(self.__store) do
table.insert(keyPairs, {key, value})
end

if direction == Enum.SortDirection.Ascending then
table.sort(keyPairs, sortPairsAscending)
elseif direction == Enum.SortDirection.Descending then
table.sort(keyPairs, sortPairsDescending)
end
end

return MockMemoryStoreSortedMap
15 changes: 15 additions & 0 deletions lib/MockMemoryStoreService/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
local MockMemoryStoreSortedMap = require(script.MockMemoryStoreSortedMap)
local MockMemoryStoreQueue = require(script.MockMemoryStoreQueue)

local MockMemoryStoreService = {}
MockMemoryStoreService.__index = MockMemoryStoreService

function MockMemoryStoreService:GetSortedMap(name: string)
return MockMemoryStoreSortedMap.new(name)
end

function MockMemoryStoreService:GetQueue(name: string, timeout: number)
return MockMemoryStoreQueue.new(name, timeout)
end

return MockMemoryStoreService
8 changes: 8 additions & 0 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import "@rbxts/types";
/*
Since MockMemoryStoreService is
designed to mock MemoryStoreService,
we can just use its typings.
*/
declare const MemoryStoreWrapper: MemoryStoreService;
export = MemoryStoreWrapper;
23 changes: 23 additions & 0 deletions lib/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
local MockMemoryStoreServiceModule = script.MockMemoryStoreService

local shouldUseMock = false
if game.GameId == 0 then
shouldUseMock = true
elseif game:GetService("RunService"):IsStudio() then
local status, message = pcall(function()
-- This will error if current instance has no Studio API access:
game:GetService("MemoryStoreService"):GetSortedMap("__TEST")
end)
if not status and message:find("403", 1, true) then -- HACK
-- Can connect to datastores, but no API access
shouldUseMock = true
end
end

-- Return the mock or actual service depending on environment:
if shouldUseMock then
warn("INFO: Using MockMemoryStoreService instead of MemoryStoreService")
return require(MockMemoryStoreServiceModule)
else
return game:GetService("MemoryStoreService")
end
43 changes: 43 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@rbxts/mockmemorystoreservice",
"version": "1.0.0",
"description": "A wrapper for memory store service to allow testing locally",
"main": "lib/init.lua",
"scripts": {
"test": "rojo build tests.project.json --output tests.rbxl && run-in-roblox --place tests.rbxl --script ./scripts/tests.lua"
},
"author": "",
"license": "ISC",
"dependencies": {
"@rbxts/types": "^1.0.569"
},
"devDependencies": {
"@rbxts/testez": "^0.3.1-ts.7"
}
}

0 comments on commit 690234d

Please sign in to comment.