NicePack/sources/Data/NiceStorageServerBase.uc
2020-02-16 19:53:59 +07:00

206 lines
7.2 KiB
Ucode

//==============================================================================
// NicePack / NiceStorageServerBase
//==============================================================================
// Implements storage methods relevant only to server.
//==============================================================================
// 'Nice pack' source
// Do whatever the fuck you want with it
// Author: dkanus
// E-mail: dkanus@gmail.com
//==============================================================================
class NiceStorageServerBase extends NiceStorageBase
abstract
config(NicePack);
struct ReplicationRequest{
var string dataID;
var string variable;
};
struct RequestQueue{
// Elements with indices below this one were already replicated
var int newIndex;
var array<ReplicationRequest> requests;
// All changes must preserve following invariants:
// - newIndex >= 0
// - newIndex <= requests.length
};
struct ClientConnection{
var NicePlayerController player;
var float replicationCountdown;
var RequestQueue lowPriorityQueue;
var RequestQueue highPriorityQueue;
};
// List of all the players who are connected to any of our data
var protected array<ClientConnection> connections;
// How much time needs to pass before we send new data;
// Each client has it's own cooldowns;
// Not applicable to 'NSP_REALTIME' priority
// since everything is replicated immediately then.
var config float replicationCooldown;
//==============================================================================
// > Variables related to purging 'none' actors
// After client disconnects - it's reference will only uselessly
// clutter connection or listeners references, -
// that's why we need to do periodic "clean ups".
// Time between purges
var config float cleanupCooldown;
// We clear all lost connections every purge, but only this many data sets
var config int cleanupPassesPerRound;
var protected float cleanupCountDown;
// Next index of the next data to be cleaned
var protected int cleanupNextDataIndex;
function bool CreateData(string ID, NiceData.EDataPriority priority){
local NiceServerData serverData;
if(ID == "") return false;
if(DoesDataExistLocally(ID)) return false;
serverData = NiceServerData(class'NiceServerData'.static.NewData(ID));
if(!StoreData(serverData, priority))
return false;
return true;
}
// Puts given with data in the storage without any synchronization work.
// Can fail if data with the same ID already exists.
function bool StoreData(NiceData data, NiceData.EDataPriority priority){
local string ID;
local NiceServerData serverData;
serverData = NiceServerData(data);
if(serverData == none) return false;
ID = serverData.GetID();
if(DoesDataExistLocally(ID))
return false;
localStorage[localStorage.length] = serverData;
serverData.SetOwnerStorage(V(ID), self);
serverData.SetPriority(V(ID), priority);
events.static.CallDataCreated(ID);
return true;
}
function bool CanGrantWriteRights( NiceServerData data,
NicePlayerController clientRef){
local bool isClientAdmin;
if(data == none) return false;
if(data.GetWriteRightsOwner() != none) return false;
// Admin rights check
isClientAdmin = false;
if(clientRef != none && clientRef.PlayerReplicationInfo != none)
isClientAdmin = clientRef.PlayerReplicationInfo.bAdmin;
if(data.isAdminOnly && !isClientAdmin)
return false;
return true;
}
// #private
function bool OpenWriteAccess(DataRef dataRef, NicePlayerController niceClient){
local NiceServerData data;
if(niceClient == none || niceClient.remoteRI == none) return false;
data = NiceServerData(GetData(dataRef.ID));
if(data == none)
return false;
if(CanGrantWriteRights(data, niceClient)){
data.SetWriteRightsOwner(dataRef, niceClient);
events.static.CallWriteAccessGranted(dataRef.ID, niceClient);
niceClient.remoteRI.ClientOpenWriteRights(dataRef);
return true;
}
events.static.CallWriteAccessRefused( dataRef.ID,
data.GetWriteRightsOwner());
niceClient.remoteRI.ClientRefuseWriteRights(dataRef);
return false;
}
// #private
function bool CloseWriteAccess(DataRef dataRef){
local NiceServerData data;
local NicePlayerController oldOwner;
data = NiceServerData(GetData(dataRef.ID));
if(data == none)
return false;
oldOwner = data.GetWriteRightsOwner();
if(oldOwner == none)
return false;
data.SetWriteRightsOwner(dataRef, none);
events.static.CallWritingAccessRevoked(dataRef.ID, oldOwner);
if(oldOwner.remoteRI != none)
oldOwner.remoteRI.ClientCloseWriteRights(dataRef);
return true;
}
function AddConnection(NicePlayerController clientRef){
local int i;
local int newIndex;
local ClientConnection newConnection;
if(clientRef == none) return;
for(i = 0;i < connections.length;i ++)
if(connections[i].player == clientRef)
return;
newConnection.player = clientRef;
newConnection.lowPriorityQueue.newIndex = 0;
newConnection.highPriorityQueue.newIndex = 0;
newIndex = connections.length;
connections[newIndex] = newConnection;
}
// Returns index for a connection for 'clientRef',
// returns -1 if there's no connection for it.
protected function int FindConnection(NicePlayerController clientRef){
local int i;
// Connection can contain 'none' values due to players disconnecting,
if(clientRef == none)
return -1;
for(i = 0;i < connections.length;i ++)
if(connections[i].player == clientRef)
return i;
return -1;
}
protected function CleanupConnections(){
local int i;
local array<ClientConnection> newConnections;
for(i = 0;i < connections.length;i ++)
if(connections[i].player != none)
newConnections[newConnections.length] = connections[i];
connections = newConnections;
}
// There might be a potentially huge number of data with listeners,
// so we'll clean only a certain amount of the at a time.
protected function DoCleanupListenersRound(int passesAmount){
local NiceServerData serverData;
if(localStorage.length <= 0) return;
if(cleanupNextDataIndex < 0 || cleanupNextDataIndex >= localStorage.length)
cleanupNextDataIndex = 0;
serverData = NiceServerData(localStorage[cleanupNextDataIndex]);
if(serverData != none)
serverData.PurgeNullListeners(V(serverData.GetID()));
cleanupNextDataIndex ++;
DoCleanupListenersRound(passesAmount - 1);
}
function Tick(float delta){
cleanupCountDown -= delta;
if(cleanupCountDown <= 0){
cleanupCountDown = cleanupCooldown;
CleanupConnections();
DoCleanupListenersRound(cleanupPassesPerRound);
}
}
defaultproperties
{
dataClass=class'NiceServerData'
cleanupCooldown=1.0
cleanupPassesPerRound=10
replicationCooldown=0.025
}