From 89d9216b5934ff967b57192377c4c5887a0bccde Mon Sep 17 00:00:00 2001 From: Anton Tarasenko Date: Thu, 22 Apr 2021 15:39:15 +0700 Subject: [PATCH] Change how new players are tracked --- sources/Global.uc | 2 + sources/Players/APlayer.uc | 21 ++-- sources/Players/ConnectionListener_Player.uc | 52 ---------- sources/Players/PlayerEvents.uc | 51 ---------- sources/Players/PlayerService.uc | 56 +++++------ sources/Players/PlayersAPI.uc | 99 +++++++++++++++++++ .../Services/Connection/ConnectionEvents.uc | 53 ---------- .../Services/Connection/ConnectionService.uc | 57 ++++++++--- .../Connection_Signal.uc} | 37 +++---- .../Connection/Events/Connection_Slot.uc} | 36 +++---- 10 files changed, 215 insertions(+), 249 deletions(-) delete mode 100644 sources/Players/ConnectionListener_Player.uc delete mode 100644 sources/Players/PlayerEvents.uc create mode 100644 sources/Players/PlayersAPI.uc delete mode 100644 sources/Services/Connection/ConnectionEvents.uc rename sources/Services/Connection/{ConnectionListenerBase.uc => Events/Connection_Signal.uc} (50%) rename sources/{Players/PlayerListenerBase.uc => Services/Connection/Events/Connection_Slot.uc} (56%) diff --git a/sources/Global.uc b/sources/Global.uc index 3f9a88b..0e046d4 100644 --- a/sources/Global.uc +++ b/sources/Global.uc @@ -37,6 +37,7 @@ var public MemoryAPI memory; var public ConsoleAPI console; var public ColorAPI color; var public UserAPI users; +var public PlayersAPI players; var public JSONAPI json; public final static function Global GetInstance() @@ -67,6 +68,7 @@ protected function Initialize() console = ConsoleAPI(memory.Allocate(class'ConsoleAPI')); color = ColorAPI(memory.Allocate(class'ColorAPI')); users = UserAPI(memory.Allocate(class'UserAPI')); + players = PlayersAPI(memory.Allocate(class'PlayersAPI')); json = JSONAPI(memory.Allocate(class'JSONAPI')); json.StaticConstructor(); } \ No newline at end of file diff --git a/sources/Players/APlayer.uc b/sources/Players/APlayer.uc index 0d9e199..8d6ca32 100644 --- a/sources/Players/APlayer.uc +++ b/sources/Players/APlayer.uc @@ -33,6 +33,8 @@ var private ConsoleWriter consoleInstance; // Remember version to reallocate writer in case someone deallocates it var private int consoleLifeVersion; +var private NativeActorRef controller; + // These variables record name of this player; // `hashedName` is used to track outside changes that bypass our getter/setter. var private Text textName; @@ -49,19 +51,9 @@ enum AdminStatus AS_SilentAdmin }; -// `PlayerController` associated with the caller `APLayer`. -// 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() +protected function Finalizer() { - local PlayerService service; - service = PlayerService(class'PlayerService'.static.Require()); - if (service != none) { - return service.GetController(self); - } - return none; + _.memory.Free(controller); } // `PlayerReplicationInfo` associated with the caller `APLayer`. @@ -72,7 +64,7 @@ private final function PlayerController GetController() private final function PlayerReplicationInfo GetRI() { local PlayerController myController; - myController = GetController(); + myController = PlayerController(controller.Get()); if (myController != none) { return myController.playerReplicationInfo; } @@ -88,7 +80,7 @@ private final function PlayerReplicationInfo GetRI() */ 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 service = PlayerService(class'PlayerService'.static.Require()); myController = service.GetController(self); + controller = _.unreal.ActorRef(myController); if (myController != none) { myReplicationInfo = myController.playerReplicationInfo; } diff --git a/sources/Players/ConnectionListener_Player.uc b/sources/Players/ConnectionListener_Player.uc deleted file mode 100644 index 607c86a..0000000 --- a/sources/Players/ConnectionListener_Player.uc +++ /dev/null @@ -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 . - */ -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.") -} \ No newline at end of file diff --git a/sources/Players/PlayerEvents.uc b/sources/Players/PlayerEvents.uc deleted file mode 100644 index 14f2be7..0000000 --- a/sources/Players/PlayerEvents.uc +++ /dev/null @@ -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 . - */ -class PlayerEvents extends Events - abstract; - -static function CallPlayerConnected(APlayer newPlayer) -{ - local int i; - local array< class > listeners; - listeners = GetListeners(); - for (i = 0; i < listeners.length; i += 1) - { - class(listeners[i]) - .static.PlayerConnected(newPlayer); - } -} - -static function CallPlayerDisconnected(APlayer lostPlayer) -{ - local int i; - local array< class > listeners; - listeners = GetListeners(); - for (i = 0; i < listeners.length; i += 1) - { - class(listeners[i]) - .static.PlayerDisconnected(lostPlayer); - } -} - -defaultproperties -{ - relatedListener = class'PlayerListenerBase' - connectedServiceClass = class'PlayerService' -} \ No newline at end of file diff --git a/sources/Players/PlayerService.uc b/sources/Players/PlayerService.uc index 869a796..7bd9049 100644 --- a/sources/Players/PlayerService.uc +++ b/sources/Players/PlayerService.uc @@ -22,61 +22,65 @@ */ 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 { var APlayer player; var PlayerController controller; }; -// Store all known players along with their `PlayerController`s. +// Records of all known pairs var private array allPlayers; -// Shortcut to `ConnectionEvents`, so that we don't have to write -// `class'ConnectionEvents'` every time. -var const class events; +protected function Contructor() +{ + SetTimer(1.0, true); +} + +protected function Finalizer() +{ + SetTimer(0.0, false); +} /** * Creates a new `APlayer` instance for a given `newPlayerController` * 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. * * @param newPlayerController Controller for which we must * create new `APlayer`. * @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 Text textIdHash; 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) { - if (allPlayers[i].controller == newPlayerController) { + if (allPlayers[i].controller == newController) { + return false; + } + if (allPlayers[i].player == newPlayer) { return false; } } // Record new pair in service's data - newPair.controller = newPlayerController; - newPair.player = APlayer(_.memory.Allocate(class'APlayer', true)); + newPair.controller = newController; + newPair.player = newPlayer; 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; } /** - * 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. */ public final function array GetAllPlayers() @@ -99,7 +103,7 @@ public final function array GetAllPlayers() * @return `APlayer` that is associated with a given `PlayerController`. * 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; if (controller == none) { @@ -143,14 +147,12 @@ public final function PlayerController GetController(APlayer player) * Causes status of all players to update. * See `APlayer.Update()` for details. */ -public final function UpdateAllPlayers() +event Timer() { local int i; while (i < allPlayers.length) { - if (allPlayers[i].controller == none) - { - events.static.CallPlayerDisconnected(allPlayers[i].player); + if (allPlayers[i].controller == none || allPlayers[i].player == none) { allPlayers.Remove(i, 1); } else { @@ -161,6 +163,4 @@ public final function UpdateAllPlayers() defaultproperties { - events = class'PlayerEvents' - requiredListeners(0) = class'ConnectionListener_Player' } \ No newline at end of file diff --git a/sources/Players/PlayersAPI.uc b/sources/Players/PlayersAPI.uc new file mode 100644 index 0000000..d658044 --- /dev/null +++ b/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 . + */ +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 GetPlayers() +{ + local PlayerService service; + local array emptyResult; + service = PlayerService(class'PlayerService'.static.GetInstance()); + if (service != none) { + return service.GetAllPlayers(); + } + return emptyResult; +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Services/Connection/ConnectionEvents.uc b/sources/Services/Connection/ConnectionEvents.uc deleted file mode 100644 index 5adcb0d..0000000 --- a/sources/Services/Connection/ConnectionEvents.uc +++ /dev/null @@ -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 . - */ -class ConnectionEvents extends Events - dependson(ConnectionService) - abstract; - -static function CallConnectionEstablished( - ConnectionService.Connection connection) -{ - local int i; - local array< class > listeners; - listeners = GetListeners(); - for (i = 0; i < listeners.length; i += 1) - { - class(listeners[i]) - .static.ConnectionEstablished(connection); - } -} - -static function CallConnectionLost(ConnectionService.Connection connection) -{ - local int i; - local array< class > listeners; - listeners = GetListeners(); - for (i = 0; i < listeners.length; i += 1) - { - class(listeners[i]) - .static.ConnectionLost(connection); - } -} - -defaultproperties -{ - relatedListener = class'ConnectionListenerBase' - connectedServiceClass = class'ConnectionService' -} \ No newline at end of file diff --git a/sources/Services/Connection/ConnectionService.uc b/sources/Services/Connection/ConnectionService.uc index c1a4088..82f8e1f 100644 --- a/sources/Services/Connection/ConnectionService.uc +++ b/sources/Services/Connection/ConnectionService.uc @@ -34,15 +34,47 @@ struct Connection var private array activeConnections; -// Shortcut to `ConnectionEvents`, so that we don't have to write -// `class'ConnectionEvents'` every time. -var const class events; +var private Connection_Signal onConnectionEstablishedSignal; +var private Connection_Signal onConnectionLostSignal; + +/** + * Signal that will be emitted when new player connection is established. + * + * [Signature] + * void (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 (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 protected function OnLaunch() { local Controller nextController; local PlayerController nextPlayerController; + onConnectionEstablishedSignal = + Connection_Signal(_.memory.Allocate(class'Connection_Signal')); + onConnectionLostSignal = + Connection_Signal(_.memory.Allocate(class'Connection_Signal')); RemoveBrokenConnections(); nextController = level.controllerList; 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` // and `controllerToCheck.playerReplicationInfo != none`. private function bool IsHumanController(PlayerController controllerToCheck) @@ -99,7 +138,7 @@ private function RemoveBrokenConnections() if (activeConnections[i].acediaRI != none) { activeConnections[i].acediaRI.Destroy(); } - events.static.CallConnectionLost(activeConnections[i]); + onConnectionLostSignal.Emit(activeConnections[i]); activeConnections.Remove(i, 1); } else { @@ -143,19 +182,14 @@ public final function bool RegisterConnection(PlayerController player) if (!IsHumanController(player)) return false; if (GetConnectionIndex(player) >= 0) return true; 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.networkAddress = player.GetPlayerNetworkAddress(); activeConnections[activeConnections.length] = newConnection; // Remember recorded connections in case someone decides to // nuke this service default.activeConnections = activeConnections; - events.static.CallConnectionEstablished(newConnection); + onConnectionEstablishedSignal.Emit(newConnection); return true; } @@ -192,6 +226,5 @@ event Tick(float delta) defaultproperties { - events = class'ConnectionEvents' requiredListeners(0) = class'MutatorListener_Connection' } \ No newline at end of file diff --git a/sources/Services/Connection/ConnectionListenerBase.uc b/sources/Services/Connection/Events/Connection_Signal.uc similarity index 50% rename from sources/Services/Connection/ConnectionListenerBase.uc rename to sources/Services/Connection/Events/Connection_Signal.uc index f82fe0c..b8de7ce 100644 --- a/sources/Services/Connection/ConnectionListenerBase.uc +++ b/sources/Services/Connection/Events/Connection_Signal.uc @@ -1,6 +1,6 @@ /** - * Listener for events generated by 'ConnectionService'. - * Copyright 2019 Anton Tarasenko + * Signal class implementation for `ConnectionServices`. + * Copyright 2021 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -17,27 +17,22 @@ * You should have received a copy of the GNU General Public License * along with Acedia. If not, see . */ -class ConnectionListenerBase extends Listener - dependson(ConnectionService) - abstract; +class Connection_Signal extends Signal; -/** - * Called the moment we detect new "established" connection: - * connection for player with already created `PlayerController` and defined - * steam data/id. - * - * @param connection Structure, describing new connection. - */ -static function ConnectionEstablished(ConnectionService.Connection connection); - -/** - * Called the moment we detect a lost connection. - * - * @param connection Structure, describing now dead connection. - */ -static function ConnectionLost(ConnectionService.Connection connection); +public final function Emit(ConnectionService.Connection connection) +{ + local Slot nextSlot; + StartIterating(); + nextSlot = GetNextSlot(); + while (nextSlot != none) + { + Connection_Slot(nextSlot).connect(connection); + nextSlot = GetNextSlot(); + } + CleanEmptySlots(); +} defaultproperties { - relatedEvents = class'ConnectionEvents' + relatedSlotClass = class'Connection_Slot' } \ No newline at end of file diff --git a/sources/Players/PlayerListenerBase.uc b/sources/Services/Connection/Events/Connection_Slot.uc similarity index 56% rename from sources/Players/PlayerListenerBase.uc rename to sources/Services/Connection/Events/Connection_Slot.uc index 49c5630..eb7ae7c 100644 --- a/sources/Players/PlayerListenerBase.uc +++ b/sources/Services/Connection/Events/Connection_Slot.uc @@ -1,6 +1,6 @@ /** - * Listener for events generated by 'ConnectionService'. - * Copyright 2019 Anton Tarasenko + * Slot class implementation for `ConnectionServices`. + * Copyright 2021 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -17,25 +17,25 @@ * You should have received a copy of the GNU General Public License * along with Acedia. If not, see . */ -class PlayerListenerBase extends Listener - abstract; +class Connection_Slot extends Slot + dependson(ConnectionService); -/** - * `PlayerConnected` is called the moment we detect a new player on a server. - * - * @param newPlayer Player that just connected. - */ -static function PlayerConnected(APlayer newPlayer); +delegate connect(ConnectionService.Connection connection) +{ + DummyCall(); +} -/** - * `PlayerDisconnected` is called the moment we detect a player leaving - * the server. - * - * @param lostPlayer Player that just disconnected. - */ -static function PlayerDisconnected(APlayer lostPlayer); +protected function Constructor() +{ + connect = none; +} + +protected function Finalizer() +{ + super.Finalizer(); + connect = none; +} defaultproperties { - relatedEvents = class'PlayerEvents' } \ No newline at end of file