From bfcf4a25d4796b7deca0c9898232d9c549470ec1 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 12 Jun 2021 21:12:39 +0100 Subject: [PATCH] Automatic cleanup of permission subscriptions on PermissibleBase destructor calls this has gotten too complex ... --- src/command/ConsoleCommandSender.php | 5 - src/permission/PermissibleBase.php | 194 +--------------- src/permission/PermissibleDelegateTrait.php | 2 +- src/permission/PermissibleInternal.php | 233 ++++++++++++++++++++ src/player/Player.php | 2 - 5 files changed, 242 insertions(+), 194 deletions(-) create mode 100644 src/permission/PermissibleInternal.php diff --git a/src/command/ConsoleCommandSender.php b/src/command/ConsoleCommandSender.php index e94d8483e..5649f9f4c 100644 --- a/src/command/ConsoleCommandSender.php +++ b/src/command/ConsoleCommandSender.php @@ -87,9 +87,4 @@ class ConsoleCommandSender implements CommandSender{ } $this->lineHeight = $height; } - - public function __destruct(){ - //permission subscriptions need to be cleaned up explicitly - $this->perm->destroyCycles(); - } } diff --git a/src/permission/PermissibleBase.php b/src/permission/PermissibleBase.php index 87a6ca04c..96e32a95f 100644 --- a/src/permission/PermissibleBase.php +++ b/src/permission/PermissibleBase.php @@ -24,202 +24,24 @@ declare(strict_types=1); namespace pocketmine\permission; use pocketmine\plugin\Plugin; -use pocketmine\plugin\PluginException; -use pocketmine\timings\Timings; use pocketmine\utils\ObjectSet; -use function count; -use function spl_object_id; -class PermissibleBase implements Permissible{ - /** - * @var bool[] - * @phpstan-var array - */ - private $rootPermissions; +final class PermissibleBase implements Permissible{ + use PermissibleDelegateTrait; - /** @var PermissionAttachment[] */ - private $attachments = []; - - /** @var PermissionAttachmentInfo[] */ - private $permissions = []; - - /** - * @var ObjectSet|\Closure[] - * @phpstan-var ObjectSet<\Closure(array $changedPermissionsOldValues) : void> - */ - private $permissionRecalculationCallbacks; + private PermissibleInternal $permissibleBase; /** * @param bool[] $basePermissions * @phpstan-param array $basePermissions */ public function __construct(array $basePermissions){ - $this->permissionRecalculationCallbacks = new ObjectSet(); - - //TODO: we can't setBasePermission here directly due to bad architecture that causes recalculatePermissions to explode - //so, this hack has to be done here to prevent permission recalculations until it's fixed... - $this->rootPermissions = $basePermissions; - //TODO: permissions need to be recalculated here, or inherited permissions won't work + $this->permissibleBase = new PermissibleInternal($basePermissions); + $this->perm = $this->permissibleBase; } - public function setBasePermission($name, bool $grant) : void{ - if($name instanceof Permission){ - $name = $name->getName(); - } - $this->rootPermissions[$name] = $grant; - $this->recalculatePermissions(); - } - - public function unsetBasePermission($name) : void{ - unset($this->rootPermissions[$name instanceof Permission ? $name->getName() : $name]); - $this->recalculatePermissions(); - } - - /** - * @param Permission|string $name - */ - public function isPermissionSet($name) : bool{ - return isset($this->permissions[$name instanceof Permission ? $name->getName() : $name]); - } - - /** - * @param Permission|string $name - */ - public function hasPermission($name) : bool{ - if($name instanceof Permission){ - $name = $name->getName(); - } - - if($this->isPermissionSet($name)){ - return $this->permissions[$name]->getValue(); - } - - return false; - } - - /** - * //TODO: tick scheduled attachments - */ - public function addAttachment(Plugin $plugin, ?string $name = null, ?bool $value = null) : PermissionAttachment{ - if(!$plugin->isEnabled()){ - throw new PluginException("Plugin " . $plugin->getDescription()->getName() . " is disabled"); - } - - $result = new PermissionAttachment($plugin); - $this->attachments[spl_object_id($result)] = $result; - if($name !== null and $value !== null){ - $result->setPermission($name, $value); - } - - $result->subscribePermissible($this); - - $this->recalculatePermissions(); - - return $result; - } - - public function removeAttachment(PermissionAttachment $attachment) : void{ - if(isset($this->attachments[spl_object_id($attachment)])){ - unset($this->attachments[spl_object_id($attachment)]); - $attachment->unsubscribePermissible($this); - if(($ex = $attachment->getRemovalCallback()) !== null){ - $ex->attachmentRemoved($this, $attachment); - } - - $this->recalculatePermissions(); - - } - - } - - public function recalculatePermissions() : array{ - Timings::$permissibleCalculation->startTiming(); - - $permManager = PermissionManager::getInstance(); - $permManager->unsubscribeFromAllPermissions($this); - $oldPermissions = $this->permissions; - $this->permissions = []; - - foreach($this->rootPermissions as $name => $isGranted){ - $perm = $permManager->getPermission($name); - if($perm === null){ - throw new \InvalidStateException("Unregistered root permission $name"); - } - $this->permissions[$name] = new PermissionAttachmentInfo($name, null, $isGranted, null); - $permManager->subscribeToPermission($name, $this); - $this->calculateChildPermissions($perm->getChildren(), !$isGranted, null, $this->permissions[$name]); - } - - foreach($this->attachments as $attachment){ - $this->calculateChildPermissions($attachment->getPermissions(), false, $attachment, null); - } - - $diff = []; - Timings::$permissibleCalculationDiff->time(function() use ($oldPermissions, &$diff) : void{ - foreach($this->permissions as $permissionAttachmentInfo){ - $name = $permissionAttachmentInfo->getPermission(); - if(!isset($oldPermissions[$name])){ - $diff[$name] = false; - }elseif($oldPermissions[$name]->getValue() !== $permissionAttachmentInfo->getValue()){ - continue; - } - unset($oldPermissions[$name]); - } - //oldPermissions now only contains permissions that changed or are no longer set - foreach($oldPermissions as $permissionAttachmentInfo){ - $diff[$permissionAttachmentInfo->getPermission()] = $permissionAttachmentInfo->getValue(); - } - }); - - Timings::$permissibleCalculationCallback->time(function() use ($diff) : void{ - if(count($diff) > 0){ - foreach($this->permissionRecalculationCallbacks as $closure){ - $closure($diff); - } - } - }); - - Timings::$permissibleCalculation->stopTiming(); - return $diff; - } - - /** - * @param bool[] $children - */ - private function calculateChildPermissions(array $children, bool $invert, ?PermissionAttachment $attachment, ?PermissionAttachmentInfo $parent) : void{ - $permManager = PermissionManager::getInstance(); - foreach($children as $name => $v){ - $perm = $permManager->getPermission($name); - $value = ($v xor $invert); - $this->permissions[$name] = new PermissionAttachmentInfo($name, $attachment, $value, $parent); - $permManager->subscribeToPermission($name, $this); - - if($perm instanceof Permission){ - $this->calculateChildPermissions($perm->getChildren(), !$value, $attachment, $this->permissions[$name]); - } - } - } - - /** - * @return \Closure[]|ObjectSet - * @phpstan-return ObjectSet<\Closure(array $changedPermissionsOldValues) : void> - */ - public function getPermissionRecalculationCallbacks() : ObjectSet{ return $this->permissionRecalculationCallbacks; } - - /** - * @return PermissionAttachmentInfo[] - */ - public function getEffectivePermissions() : array{ - return $this->permissions; - } - - public function destroyCycles() : void{ - PermissionManager::getInstance()->unsubscribeFromAllPermissions($this); - $this->permissions = []; //PermissionAttachmentInfo doesn't reference Permissible anymore, but it references PermissionAttachment which does - foreach($this->attachments as $attachment){ - $attachment->unsubscribePermissible($this); - } - $this->attachments = []; - $this->permissionRecalculationCallbacks->clear(); + public function __destruct(){ + //permission subscriptions need to be cleaned up explicitly + $this->permissibleBase->destroyCycles(); } } diff --git a/src/permission/PermissibleDelegateTrait.php b/src/permission/PermissibleDelegateTrait.php index be3e8da91..d9de362f2 100644 --- a/src/permission/PermissibleDelegateTrait.php +++ b/src/permission/PermissibleDelegateTrait.php @@ -28,7 +28,7 @@ use pocketmine\utils\ObjectSet; trait PermissibleDelegateTrait{ - /** @var PermissibleBase */ + /** @var Permissible */ private $perm; /** diff --git a/src/permission/PermissibleInternal.php b/src/permission/PermissibleInternal.php new file mode 100644 index 000000000..2b0bce8dc --- /dev/null +++ b/src/permission/PermissibleInternal.php @@ -0,0 +1,233 @@ + + */ + private $rootPermissions; + + /** @var PermissionAttachment[] */ + private $attachments = []; + + /** @var PermissionAttachmentInfo[] */ + private $permissions = []; + + /** + * @var ObjectSet|\Closure[] + * @phpstan-var ObjectSet<\Closure(array $changedPermissionsOldValues) : void> + */ + private $permissionRecalculationCallbacks; + + /** + * @param bool[] $basePermissions + * @phpstan-param array $basePermissions + */ + public function __construct(array $basePermissions){ + $this->permissionRecalculationCallbacks = new ObjectSet(); + + //TODO: we can't setBasePermission here directly due to bad architecture that causes recalculatePermissions to explode + //so, this hack has to be done here to prevent permission recalculations until it's fixed... + $this->rootPermissions = $basePermissions; + //TODO: permissions need to be recalculated here, or inherited permissions won't work + } + + public function setBasePermission($name, bool $grant) : void{ + if($name instanceof Permission){ + $name = $name->getName(); + } + $this->rootPermissions[$name] = $grant; + $this->recalculatePermissions(); + } + + public function unsetBasePermission($name) : void{ + unset($this->rootPermissions[$name instanceof Permission ? $name->getName() : $name]); + $this->recalculatePermissions(); + } + + /** + * @param Permission|string $name + */ + public function isPermissionSet($name) : bool{ + return isset($this->permissions[$name instanceof Permission ? $name->getName() : $name]); + } + + /** + * @param Permission|string $name + */ + public function hasPermission($name) : bool{ + if($name instanceof Permission){ + $name = $name->getName(); + } + + if($this->isPermissionSet($name)){ + return $this->permissions[$name]->getValue(); + } + + return false; + } + + /** + * //TODO: tick scheduled attachments + */ + public function addAttachment(Plugin $plugin, ?string $name = null, ?bool $value = null) : PermissionAttachment{ + if(!$plugin->isEnabled()){ + throw new PluginException("Plugin " . $plugin->getDescription()->getName() . " is disabled"); + } + + $result = new PermissionAttachment($plugin); + $this->attachments[spl_object_id($result)] = $result; + if($name !== null and $value !== null){ + $result->setPermission($name, $value); + } + + $result->subscribePermissible($this); + + $this->recalculatePermissions(); + + return $result; + } + + public function removeAttachment(PermissionAttachment $attachment) : void{ + if(isset($this->attachments[spl_object_id($attachment)])){ + unset($this->attachments[spl_object_id($attachment)]); + $attachment->unsubscribePermissible($this); + if(($ex = $attachment->getRemovalCallback()) !== null){ + $ex->attachmentRemoved($this, $attachment); + } + + $this->recalculatePermissions(); + + } + + } + + public function recalculatePermissions() : array{ + Timings::$permissibleCalculation->startTiming(); + + $permManager = PermissionManager::getInstance(); + $permManager->unsubscribeFromAllPermissions($this); + $oldPermissions = $this->permissions; + $this->permissions = []; + + foreach($this->rootPermissions as $name => $isGranted){ + $perm = $permManager->getPermission($name); + if($perm === null){ + throw new \InvalidStateException("Unregistered root permission $name"); + } + $this->permissions[$name] = new PermissionAttachmentInfo($name, null, $isGranted, null); + $permManager->subscribeToPermission($name, $this); + $this->calculateChildPermissions($perm->getChildren(), !$isGranted, null, $this->permissions[$name]); + } + + foreach($this->attachments as $attachment){ + $this->calculateChildPermissions($attachment->getPermissions(), false, $attachment, null); + } + + $diff = []; + Timings::$permissibleCalculationDiff->time(function() use ($oldPermissions, &$diff) : void{ + foreach($this->permissions as $permissionAttachmentInfo){ + $name = $permissionAttachmentInfo->getPermission(); + if(!isset($oldPermissions[$name])){ + $diff[$name] = false; + }elseif($oldPermissions[$name]->getValue() !== $permissionAttachmentInfo->getValue()){ + continue; + } + unset($oldPermissions[$name]); + } + //oldPermissions now only contains permissions that changed or are no longer set + foreach($oldPermissions as $permissionAttachmentInfo){ + $diff[$permissionAttachmentInfo->getPermission()] = $permissionAttachmentInfo->getValue(); + } + }); + + Timings::$permissibleCalculationCallback->time(function() use ($diff) : void{ + if(count($diff) > 0){ + foreach($this->permissionRecalculationCallbacks as $closure){ + $closure($diff); + } + } + }); + + Timings::$permissibleCalculation->stopTiming(); + return $diff; + } + + /** + * @param bool[] $children + */ + private function calculateChildPermissions(array $children, bool $invert, ?PermissionAttachment $attachment, ?PermissionAttachmentInfo $parent) : void{ + $permManager = PermissionManager::getInstance(); + foreach($children as $name => $v){ + $perm = $permManager->getPermission($name); + $value = ($v xor $invert); + $this->permissions[$name] = new PermissionAttachmentInfo($name, $attachment, $value, $parent); + $permManager->subscribeToPermission($name, $this); + + if($perm instanceof Permission){ + $this->calculateChildPermissions($perm->getChildren(), !$value, $attachment, $this->permissions[$name]); + } + } + } + + /** + * @return \Closure[]|ObjectSet + * @phpstan-return ObjectSet<\Closure(array $changedPermissionsOldValues) : void> + */ + public function getPermissionRecalculationCallbacks() : ObjectSet{ return $this->permissionRecalculationCallbacks; } + + /** + * @return PermissionAttachmentInfo[] + */ + public function getEffectivePermissions() : array{ + return $this->permissions; + } + + public function destroyCycles() : void{ + PermissionManager::getInstance()->unsubscribeFromAllPermissions($this); + $this->permissions = []; //PermissionAttachmentInfo doesn't reference Permissible anymore, but it references PermissionAttachment which does + foreach($this->attachments as $attachment){ + $attachment->unsubscribePermissible($this); + } + $this->attachments = []; + $this->permissionRecalculationCallbacks->clear(); + } +} diff --git a/src/player/Player.php b/src/player/Player.php index ed8a706f4..9cf9ba72d 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -2005,8 +2005,6 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $this->removeCurrentWindow(); $this->removePermanentInventories(); - $this->perm->destroyCycles(); - $this->flagForDespawn(); }