Browse Source

Change how new players are tracked

pull/8/head
Anton Tarasenko 4 years ago
parent
commit
89d9216b59
  1. 2
      sources/Global.uc
  2. 21
      sources/Players/APlayer.uc
  3. 52
      sources/Players/ConnectionListener_Player.uc
  4. 51
      sources/Players/PlayerEvents.uc
  5. 56
      sources/Players/PlayerService.uc
  6. 99
      sources/Players/PlayersAPI.uc
  7. 53
      sources/Services/Connection/ConnectionEvents.uc
  8. 57
      sources/Services/Connection/ConnectionService.uc
  9. 37
      sources/Services/Connection/Events/Connection_Signal.uc
  10. 36
      sources/Services/Connection/Events/Connection_Slot.uc

2
sources/Global.uc

@ -37,6 +37,7 @@ var public MemoryAPI memory;
var public ConsoleAPI console; var public ConsoleAPI console;
var public ColorAPI color; var public ColorAPI color;
var public UserAPI users; var public UserAPI users;
var public PlayersAPI players;
var public JSONAPI json; var public JSONAPI json;
public final static function Global GetInstance() public final static function Global GetInstance()
@ -67,6 +68,7 @@ protected function Initialize()
console = ConsoleAPI(memory.Allocate(class'ConsoleAPI')); console = ConsoleAPI(memory.Allocate(class'ConsoleAPI'));
color = ColorAPI(memory.Allocate(class'ColorAPI')); color = ColorAPI(memory.Allocate(class'ColorAPI'));
users = UserAPI(memory.Allocate(class'UserAPI')); users = UserAPI(memory.Allocate(class'UserAPI'));
players = PlayersAPI(memory.Allocate(class'PlayersAPI'));
json = JSONAPI(memory.Allocate(class'JSONAPI')); json = JSONAPI(memory.Allocate(class'JSONAPI'));
json.StaticConstructor(); json.StaticConstructor();
} }

21
sources/Players/APlayer.uc

@ -33,6 +33,8 @@ var private ConsoleWriter consoleInstance;
// Remember version to reallocate writer in case someone deallocates it // Remember version to reallocate writer in case someone deallocates it
var private int consoleLifeVersion; var private int consoleLifeVersion;
var private NativeActorRef controller;
// These variables record name of this player; // These variables record name of this player;
// `hashedName` is used to track outside changes that bypass our getter/setter. // `hashedName` is used to track outside changes that bypass our getter/setter.
var private Text textName; var private Text textName;
@ -49,19 +51,9 @@ enum AdminStatus
AS_SilentAdmin AS_SilentAdmin
}; };
// `PlayerController` associated with the caller `APLayer`. protected function Finalizer()
// Can return `none` if:
// 1. Caller `APlayer` has already disconnected;
// 2. It was not properly initialized;
// 3. There is an issue running `PlayerService`.
private final function PlayerController GetController()
{ {
local PlayerService service; _.memory.Free(controller);
service = PlayerService(class'PlayerService'.static.Require());
if (service != none) {
return service.GetController(self);
}
return none;
} }
// `PlayerReplicationInfo` associated with the caller `APLayer`. // `PlayerReplicationInfo` associated with the caller `APLayer`.
@ -72,7 +64,7 @@ private final function PlayerController GetController()
private final function PlayerReplicationInfo GetRI() private final function PlayerReplicationInfo GetRI()
{ {
local PlayerController myController; local PlayerController myController;
myController = GetController(); myController = PlayerController(controller.Get());
if (myController != none) { if (myController != none) {
return myController.playerReplicationInfo; return myController.playerReplicationInfo;
} }
@ -88,7 +80,7 @@ private final function PlayerReplicationInfo GetRI()
*/ */
public final function bool IsConnected() public final function bool IsConnected()
{ {
return (GetController() != none); return (controller.Get() != none);
} }
/** /**
@ -110,6 +102,7 @@ public final function Initialize(Text idHash)
// Retrieve controller and replication info // Retrieve controller and replication info
service = PlayerService(class'PlayerService'.static.Require()); service = PlayerService(class'PlayerService'.static.Require());
myController = service.GetController(self); myController = service.GetController(self);
controller = _.unreal.ActorRef(myController);
if (myController != none) { if (myController != none) {
myReplicationInfo = myController.playerReplicationInfo; myReplicationInfo = myController.playerReplicationInfo;
} }

52
sources/Players/ConnectionListener_Player.uc

@ -1,52 +0,0 @@
/**
* `PlayerService`'s listener for events generated by 'ConnectionService'.
* Copyright 2019 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class ConnectionListener_Player extends ConnectionListenerBase;
var LoggerAPI.Definition fatalNoPlayerService;
static function ConnectionEstablished(ConnectionService.Connection connection)
{
local PlayerService service;
service = PlayerService(class'PlayerService'.static.Require());
if (service == none)
{
__().logger.Auto(default.fatalNoPlayerService);
return;
}
service.RegisterPlayer(connection.controllerReference);
}
static function ConnectionLost(ConnectionService.Connection connection)
{
local PlayerService service;
service = PlayerService(class'PlayerService'.static.Require());
if (service == none)
{
__().logger.Auto(default.fatalNoPlayerService);
return;
}
service.UpdateAllPlayers();
}
defaultproperties
{
relatedEvents = class'ConnectionEvents'
fatalNoPlayerService = (l=LOG_Fatal,m="Cannot start `PlayerService` service Acedia will not properly work from now on.")
}

51
sources/Players/PlayerEvents.uc

@ -1,51 +0,0 @@
/**
* Event generator for 'ConnectionService'.
* Copyright 2019 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class PlayerEvents extends Events
abstract;
static function CallPlayerConnected(APlayer newPlayer)
{
local int i;
local array< class<Listener> > listeners;
listeners = GetListeners();
for (i = 0; i < listeners.length; i += 1)
{
class<PlayerListenerBase>(listeners[i])
.static.PlayerConnected(newPlayer);
}
}
static function CallPlayerDisconnected(APlayer lostPlayer)
{
local int i;
local array< class<Listener> > listeners;
listeners = GetListeners();
for (i = 0; i < listeners.length; i += 1)
{
class<PlayerListenerBase>(listeners[i])
.static.PlayerDisconnected(lostPlayer);
}
}
defaultproperties
{
relatedListener = class'PlayerListenerBase'
connectedServiceClass = class'PlayerService'
}

56
sources/Players/PlayerService.uc

@ -22,61 +22,65 @@
*/ */
class PlayerService extends Service; class PlayerService extends Service;
// Used to 1-to-1 associate `APlayer` with `PlayerController` object. // Used to 1-to-1 associate `APlayer` objects with `PlayerController` actors.
struct PlayerControllerPair struct PlayerControllerPair
{ {
var APlayer player; var APlayer player;
var PlayerController controller; var PlayerController controller;
}; };
// Store all known players along with their `PlayerController`s. // Records of all known pairs
var private array<PlayerControllerPair> allPlayers; var private array<PlayerControllerPair> allPlayers;
// Shortcut to `ConnectionEvents`, so that we don't have to write protected function Contructor()
// `class'ConnectionEvents'` every time. {
var const class<PlayerEvents> events; SetTimer(1.0, true);
}
protected function Finalizer()
{
SetTimer(0.0, false);
}
/** /**
* Creates a new `APlayer` instance for a given `newPlayerController` * Creates a new `APlayer` instance for a given `newPlayerController`
* controller. * controller.
* *
* If given controller is `none` or it's `APLayer` was already created, * If given controller is `none` or it's `APlayer` was already created,
* - does nothing. * - does nothing.
* *
* @param newPlayerController Controller for which we must * @param newPlayerController Controller for which we must
* create new `APlayer`. * create new `APlayer`.
* @return `true` if new `APlayer` was created and `false` otherwise. * @return `true` if new `APlayer` was created and `false` otherwise.
*/ */
public final function bool RegisterPlayer(PlayerController newPlayerController) public final function bool RegisterPair(
PlayerController newController,
APlayer newPlayer)
{ {
local int i; local int i;
local Text textIdHash;
local PlayerControllerPair newPair; local PlayerControllerPair newPair;
if (newPlayerController == none) return false; if (newController == none) return false;
if (newPlayer == none) return false;
UpdateAllPlayers();
for (i = 0; i < allPlayers.length; i += 1) for (i = 0; i < allPlayers.length; i += 1)
{ {
if (allPlayers[i].controller == newPlayerController) { if (allPlayers[i].controller == newController) {
return false;
}
if (allPlayers[i].player == newPlayer) {
return false; return false;
} }
} }
// Record new pair in service's data // Record new pair in service's data
newPair.controller = newPlayerController; newPair.controller = newController;
newPair.player = APlayer(_.memory.Allocate(class'APlayer', true)); newPair.player = newPlayer;
allPlayers[allPlayers.length] = newPair; allPlayers[allPlayers.length] = newPair;
// Initialize new `APlayer`
textIdHash = _.text.FromString(newPlayerController.GetPlayerIDHash());
newPair.player.Initialize(textIdHash);
textIdHash.FreeSelf();
// Run events
events.static.CallPlayerConnected(newPair.player);
return true; return true;
} }
/** /**
* Fetches current array of all player (registered `APLayer`s). * Fetches current array of all players (registered `APlayer`s).
* *
* @return Current array of all player (registered `APLayer`s). Guaranteed to * @return Current array of all players (registered `APlayer`s). Guaranteed to
* not contain `none` values. * not contain `none` values.
*/ */
public final function array<APlayer> GetAllPlayers() public final function array<APlayer> GetAllPlayers()
@ -99,7 +103,7 @@ public final function array<APlayer> GetAllPlayers()
* @return `APlayer` that is associated with a given `PlayerController`. * @return `APlayer` that is associated with a given `PlayerController`.
* Can return `none` if player has already "expired". * Can return `none` if player has already "expired".
*/ */
public final function APlayer GetPlayer(PlayerController controller) public final function APlayer GetPlayer(Controller controller)
{ {
local int i; local int i;
if (controller == none) { if (controller == none) {
@ -143,14 +147,12 @@ public final function PlayerController GetController(APlayer player)
* Causes status of all players to update. * Causes status of all players to update.
* See `APlayer.Update()` for details. * See `APlayer.Update()` for details.
*/ */
public final function UpdateAllPlayers() event Timer()
{ {
local int i; local int i;
while (i < allPlayers.length) while (i < allPlayers.length)
{ {
if (allPlayers[i].controller == none) if (allPlayers[i].controller == none || allPlayers[i].player == none) {
{
events.static.CallPlayerDisconnected(allPlayers[i].player);
allPlayers.Remove(i, 1); allPlayers.Remove(i, 1);
} }
else { else {
@ -161,6 +163,4 @@ public final function UpdateAllPlayers()
defaultproperties defaultproperties
{ {
events = class'PlayerEvents'
requiredListeners(0) = class'ConnectionListener_Player'
} }

99
sources/Players/PlayersAPI.uc

@ -0,0 +1,99 @@
/**
* API that provides functions for working player references (`APlayer`).
* Copyright 2021 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class PlayersAPI extends AcediaObject
dependson(Text);
// Writer that can be used to write into this player's console
var private ConsoleWriter consoleInstance;
// Remember version to reallocate writer in case someone deallocates it
var private int consoleLifeVersion;
protected function Constructor()
{
local ConnectionService service;
service = ConnectionService(class'ConnectionService'.static.Require());
service.OnConnectionEstablished(self).connect = MakePlayer;
}
protected function Finalizer()
{
local ConnectionService service;
service = ConnectionService(class'ConnectionService'.static.Require());
service.OnConnectionEstablished(self).Disconnect();
}
private final function MakePlayer(ConnectionService.Connection newConnection)
{
local APlayer newPlayer;
local Text textIdHash;
local PlayerService service;
// Make new player controller and link it to `newConnection`
newPlayer = APlayer(_.memory.Allocate(class'APlayer'));
service = PlayerService(class'PlayerService'.static.Require());
service.RegisterPair(newConnection.controllerReference, newPlayer);
// Initialize new `APlayer`
textIdHash = _.text.FromString(newConnection.idHash);
newPlayer.Initialize(textIdHash);
textIdHash.FreeSelf();
}
/**
* Return `ConsoleWriter` that can be used to write into every player's
* console.
*
* Provided that returned object is never deallocated - returns the same object
* with each call, otherwise can allocate new instance of `ConsoleWriter`.
*
* @return `ConsoleWriter` that can be used to write into every player's
* console. Returned object should not be deallocated, but it is
* guaranteed to be valid for non-disconnected players.
*/
public final function ConsoleWriter Console()
{
if ( consoleInstance == none
|| consoleInstance.GetLifeVersion() != consoleLifeVersion)
{
consoleInstance = _.console.ForAll();
consoleLifeVersion = consoleInstance.GetLifeVersion();
}
// Set everybody as a target in case someone messed with this setting
return consoleInstance.ForAll();
}
/**
* Fetches current array of all players.
*
* @return Current array of all players.
* Guaranteed to not contain `none` values.
*/
public final function array<APlayer> GetPlayers()
{
local PlayerService service;
local array<APlayer> emptyResult;
service = PlayerService(class'PlayerService'.static.GetInstance());
if (service != none) {
return service.GetAllPlayers();
}
return emptyResult;
}
defaultproperties
{
}

53
sources/Services/Connection/ConnectionEvents.uc

@ -1,53 +0,0 @@
/**
* Event generator for 'ConnectionService'.
* Copyright 2019 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class ConnectionEvents extends Events
dependson(ConnectionService)
abstract;
static function CallConnectionEstablished(
ConnectionService.Connection connection)
{
local int i;
local array< class<Listener> > listeners;
listeners = GetListeners();
for (i = 0; i < listeners.length; i += 1)
{
class<ConnectionListenerBase>(listeners[i])
.static.ConnectionEstablished(connection);
}
}
static function CallConnectionLost(ConnectionService.Connection connection)
{
local int i;
local array< class<Listener> > listeners;
listeners = GetListeners();
for (i = 0; i < listeners.length; i += 1)
{
class<ConnectionListenerBase>(listeners[i])
.static.ConnectionLost(connection);
}
}
defaultproperties
{
relatedListener = class'ConnectionListenerBase'
connectedServiceClass = class'ConnectionService'
}

57
sources/Services/Connection/ConnectionService.uc

@ -34,15 +34,47 @@ struct Connection
var private array<Connection> activeConnections; var private array<Connection> activeConnections;
// Shortcut to `ConnectionEvents`, so that we don't have to write var private Connection_Signal onConnectionEstablishedSignal;
// `class'ConnectionEvents'` every time. var private Connection_Signal onConnectionLostSignal;
var const class<ConnectionEvents> events;
/**
* Signal that will be emitted when new player connection is established.
*
* [Signature]
* void <slot>(ConnectionService.Connection newConnection)
*
* @param newConnection Structure that describes new connection.
*/
/* SIGNAL */
public final function Connection_Slot OnConnectionEstablished(
AcediaObject receiver)
{
return Connection_Slot(onConnectionEstablishedSignal.NewSlot(receiver));
}
/**
* Signal that will be emitted when the player connection is lost.
*
* [Signature]
* void <slot>(ConnectionService.Connection newConnection)
*
* @param newConnection Structure that describes lost connection.
*/
/* SIGNAL */
public final function Connection_Slot OnConnectionLost(AcediaObject receiver)
{
return Connection_Slot(onConnectionLostSignal.NewSlot(receiver));
}
// Clean disconnected and manually find all new players on launch // Clean disconnected and manually find all new players on launch
protected function OnLaunch() protected function OnLaunch()
{ {
local Controller nextController; local Controller nextController;
local PlayerController nextPlayerController; local PlayerController nextPlayerController;
onConnectionEstablishedSignal =
Connection_Signal(_.memory.Allocate(class'Connection_Signal'));
onConnectionLostSignal =
Connection_Signal(_.memory.Allocate(class'Connection_Signal'));
RemoveBrokenConnections(); RemoveBrokenConnections();
nextController = level.controllerList; nextController = level.controllerList;
while (nextController != none) while (nextController != none)
@ -55,6 +87,13 @@ protected function OnLaunch()
} }
} }
protected function OnShutdown()
{
default.activeConnections = activeConnections;
_.memory.Free(onConnectionEstablishedSignal);
_.memory.Free(onConnectionLostSignal);
}
// Returning `true` guarantees that `controllerToCheck != none` // Returning `true` guarantees that `controllerToCheck != none`
// and `controllerToCheck.playerReplicationInfo != none`. // and `controllerToCheck.playerReplicationInfo != none`.
private function bool IsHumanController(PlayerController controllerToCheck) private function bool IsHumanController(PlayerController controllerToCheck)
@ -99,7 +138,7 @@ private function RemoveBrokenConnections()
if (activeConnections[i].acediaRI != none) { if (activeConnections[i].acediaRI != none) {
activeConnections[i].acediaRI.Destroy(); activeConnections[i].acediaRI.Destroy();
} }
events.static.CallConnectionLost(activeConnections[i]); onConnectionLostSignal.Emit(activeConnections[i]);
activeConnections.Remove(i, 1); activeConnections.Remove(i, 1);
} }
else { else {
@ -143,19 +182,14 @@ public final function bool RegisterConnection(PlayerController player)
if (!IsHumanController(player)) return false; if (!IsHumanController(player)) return false;
if (GetConnectionIndex(player) >= 0) return true; if (GetConnectionIndex(player) >= 0) return true;
newConnection.controllerReference = player; newConnection.controllerReference = player;
// TODO: move this check to AcediaCore
/*if (!class'Acedia'.static.GetInstance().IsServerOnly())
{
newConnection.acediaRI = Spawn(class'AcediaReplicationInfo', player);
newConnection.acediaRI.linkOwner = player;
}*/
newConnection.idHash = player.GetPlayerIDHash(); newConnection.idHash = player.GetPlayerIDHash();
newConnection.networkAddress = player.GetPlayerNetworkAddress(); newConnection.networkAddress = player.GetPlayerNetworkAddress();
activeConnections[activeConnections.length] = newConnection; activeConnections[activeConnections.length] = newConnection;
// Remember recorded connections in case someone decides to // Remember recorded connections in case someone decides to
// nuke this service // nuke this service
default.activeConnections = activeConnections; default.activeConnections = activeConnections;
events.static.CallConnectionEstablished(newConnection); onConnectionEstablishedSignal.Emit(newConnection);
return true; return true;
} }
@ -192,6 +226,5 @@ event Tick(float delta)
defaultproperties defaultproperties
{ {
events = class'ConnectionEvents'
requiredListeners(0) = class'MutatorListener_Connection' requiredListeners(0) = class'MutatorListener_Connection'
} }

37
sources/Services/Connection/ConnectionListenerBase.uc → sources/Services/Connection/Events/Connection_Signal.uc

@ -1,6 +1,6 @@
/** /**
* Listener for events generated by 'ConnectionService'. * Signal class implementation for `ConnectionServices`.
* Copyright 2019 Anton Tarasenko * Copyright 2021 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -17,27 +17,22 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. * along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/ */
class ConnectionListenerBase extends Listener class Connection_Signal extends Signal;
dependson(ConnectionService)
abstract;
/** public final function Emit(ConnectionService.Connection connection)
* Called the moment we detect new "established" connection: {
* connection for player with already created `PlayerController` and defined local Slot nextSlot;
* steam data/id. StartIterating();
* nextSlot = GetNextSlot();
* @param connection Structure, describing new connection. while (nextSlot != none)
*/ {
static function ConnectionEstablished(ConnectionService.Connection connection); Connection_Slot(nextSlot).connect(connection);
nextSlot = GetNextSlot();
/** }
* Called the moment we detect a lost connection. CleanEmptySlots();
* }
* @param connection Structure, describing now dead connection.
*/
static function ConnectionLost(ConnectionService.Connection connection);
defaultproperties defaultproperties
{ {
relatedEvents = class'ConnectionEvents' relatedSlotClass = class'Connection_Slot'
} }

36
sources/Players/PlayerListenerBase.uc → sources/Services/Connection/Events/Connection_Slot.uc

@ -1,6 +1,6 @@
/** /**
* Listener for events generated by 'ConnectionService'. * Slot class implementation for `ConnectionServices`.
* Copyright 2019 Anton Tarasenko * Copyright 2021 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -17,25 +17,25 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. * along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/ */
class PlayerListenerBase extends Listener class Connection_Slot extends Slot
abstract; dependson(ConnectionService);
/** delegate connect(ConnectionService.Connection connection)
* `PlayerConnected` is called the moment we detect a new player on a server. {
* DummyCall();
* @param newPlayer Player that just connected. }
*/
static function PlayerConnected(APlayer newPlayer);
/** protected function Constructor()
* `PlayerDisconnected` is called the moment we detect a player leaving {
* the server. connect = none;
* }
* @param lostPlayer Player that just disconnected.
*/ protected function Finalizer()
static function PlayerDisconnected(APlayer lostPlayer); {
super.Finalizer();
connect = none;
}
defaultproperties defaultproperties
{ {
relatedEvents = class'PlayerEvents'
} }
Loading…
Cancel
Save