Skip to content

Commit

Permalink
Add playerloaded
Browse files Browse the repository at this point in the history
A library for allowing the server and other players to know when a given player loads in
  • Loading branch information
gaymeowing committed Nov 18, 2024
1 parent 0464e0e commit 7cafee3
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 0 deletions.
5 changes: 5 additions & 0 deletions libs/playerloaded/LIBRARY.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
tags = [ "Players", "Loaded", "RemoteEvent" ]
version = "1.0.0"

[runtime]
main = "roblox"
1 change: 1 addition & 0 deletions libs/playerloaded/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
### No docs site, but doc comments are provided as I'm going to be switching to moonwave at some point soon (hopefully).
198 changes: 198 additions & 0 deletions libs/playerloaded/init.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@

-- player loaded
-- module for handling callbacks to the player loaded remote

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ReplicatedFirst = game:GetService("ReplicatedFirst")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local character = require(ReplicatedFirst:WaitForChild("packages"):WaitForChild("character"))

type PlayerLoadedCallback = (player: Player & { Character: character.Character }) -> ()

local CALLBACKS_WAITING_FOR_NEXT_PLAYER = {} :: { { PlayerLoadedCallback } }
local PLAYERS_LOADED = table.create(Players.MaxPlayers) :: { Player }
local THREADS_WAITING_FOR_PLAYERS = {} :: { [Player]: { thread } }
local THREADS_WAITING_FOR_NEXT_PLAYER = {} :: { thread }
local CALLBACKS = {} :: { { PlayerLoadedCallback } }
local REMOTE_NAME = "PLAYER LOADED"
local LOADED_COUNT = 0

local function CREATE_DISCONNECT<V>(t:{ V }, v: V): () -> ()
return function()
local index = table.find(t, v)

if index then
if index ~= 1 then
local len = #t
t[index] = t[len]
t[len] = nil
else
t[1] = nil
end
end
end
end

local player_loaded = {}

--[[
Calls the provided callback everytime a player loads in,
unless disconnected using the returned disconnect function.
]]
function player_loaded.on(f: PlayerLoadedCallback, run_for_existing: true?): () -> ()
if run_for_existing then
for _, player in PLAYERS_LOADED do
task.defer(f, player :: any)
end
end

local callback_info = table.freeze({ f })
table.insert(CALLBACKS, callback_info)

return CREATE_DISCONNECT(CALLBACKS, callback_info)
end

--[[
Yields the current thread until the provided player loads in,
or if the player leaves the game it'll then resume the current thread
with nil instead of the player.
]]
function player_loaded.wait_for_player(player: Player): Player?
if not table.find(PLAYERS_LOADED, player) then
local threads = THREADS_WAITING_FOR_PLAYERS[player]

if threads then
table.insert(threads, coroutine.running())
else
THREADS_WAITING_FOR_PLAYERS[player] = { coroutine.running() }
end
return coroutine.yield()
else
return player
end
end

--[[
Calls the provided callback with the next player that loads in,
unless disconnected using the returned disconnect function.
]]
function player_loaded.once(f: PlayerLoadedCallback): () -> ()
local callback_info = table.freeze({ f })
table.insert(CALLBACKS_WAITING_FOR_NEXT_PLAYER, callback_info)

return CREATE_DISCONNECT(
CALLBACKS_WAITING_FOR_NEXT_PLAYER,
callback_info
)
end

--[[
Yields the current thread, and resumes it with the next player that loads in
]]
function player_loaded.wait(): Player
table.insert(THREADS_WAITING_FOR_NEXT_PLAYER, coroutine.running())
return coroutine.yield()
end

do

local function run_for_player(player: Player)
local threads = THREADS_WAITING_FOR_PLAYERS[player]

LOADED_COUNT += 1
PLAYERS_LOADED[LOADED_COUNT] = player

if threads then
THREADS_WAITING_FOR_PLAYERS[player] = nil

for _, thread in threads do
task.defer(thread, player)
end
end

for _, callback in CALLBACKS_WAITING_FOR_NEXT_PLAYER do
task.defer(callback[1], player :: any)
end

for _, thread in THREADS_WAITING_FOR_NEXT_PLAYER do
task.defer(thread, player)
end

for _, callback in CALLBACKS do
task.defer(callback[1], player :: any)
end

table.clear(CALLBACKS_WAITING_FOR_NEXT_PLAYER)
table.clear(THREADS_WAITING_FOR_NEXT_PLAYER)
end

if RunService:IsServer() then
local remote = Instance.new("RemoteEvent")
local fire_client = remote.FireClient
remote.Name = "PLAYER LOADED"
remote.Parent = ReplicatedStorage

remote.OnServerEvent:Connect(function(loaded_player)
if not table.find(PLAYERS_LOADED, loaded_player) then
fire_client(remote, loaded_player, PLAYERS_LOADED)
run_for_player(loaded_player)

for _, player in PLAYERS_LOADED do
fire_client(remote, player, loaded_player)
end
end
end)
else
local remote: RemoteEvent = ReplicatedStorage:WaitForChild(REMOTE_NAME) :: any

remote.OnClientEvent:Connect(function(player_or_players: Player | { Player })
if type(player_or_players) == "table" then
for _, player in player_or_players do
if not table.find(PLAYERS_LOADED, player) then
run_for_player(player)
end
end
else
run_for_player(player_or_players)
end
end)

task.defer(function()
if not game:IsLoaded() then
game.Loaded:Wait()
end

remote:FireServer()
run_for_player(Players.LocalPlayer)
end)
end

Players.PlayerRemoving:Connect(function(player)
local threads = THREADS_WAITING_FOR_PLAYERS[player]
local index = table.find(PLAYERS_LOADED, player)

if index then
if index ~= 1 then
local len = #PLAYERS_LOADED
PLAYERS_LOADED[index] = PLAYERS_LOADED[len]
PLAYERS_LOADED[len] = nil
else
PLAYERS_LOADED[1] = nil
end

LOADED_COUNT -= 1
end

if threads then
THREADS_WAITING_FOR_PLAYERS[player] = nil

for _, thread in threads do
task.defer(thread, nil)
end
end
end)

end

return table.freeze(player_loaded)

0 comments on commit 7cafee3

Please sign in to comment.