Redesign inventory listening API

given that inventory listeners are no longer allowed to fiddle with the outcome of changing contents, it's now possible to allow having multiple listeners receiving events.

It's likely that this will be used for network inventory sync in the near future.
This commit is contained in:
Dylan K. Taylor 2019-05-19 16:36:38 +01:00
parent dec6c9f49b
commit cd103cefcc
7 changed files with 145 additions and 33 deletions

View File

@ -618,8 +618,8 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
$inventoryTag = $nbt->getListTag("Inventory"); $inventoryTag = $nbt->getListTag("Inventory");
if($inventoryTag !== null){ if($inventoryTag !== null){
$armorListener = $this->armorInventory->getSlotChangeListener(); $armorListeners = $this->armorInventory->getChangeListeners();
$this->armorInventory->setSlotChangeListener(null); $this->armorInventory->removeChangeListeners(...$armorListeners);
/** @var CompoundTag $item */ /** @var CompoundTag $item */
foreach($inventoryTag as $i => $item){ foreach($inventoryTag as $i => $item){
@ -633,7 +633,7 @@ class Human extends Creature implements ProjectileSource, InventoryHolder{
} }
} }
$this->armorInventory->setSlotChangeListener($armorListener); $this->armorInventory->addChangeListeners(...$armorListeners);
} }
$enderChestInventoryTag = $nbt->getListTag("EnderChestInventory"); $enderChestInventoryTag = $nbt->getListTag("EnderChestInventory");

View File

@ -27,7 +27,6 @@ use pocketmine\event\inventory\InventoryOpenEvent;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\item\ItemFactory; use pocketmine\item\ItemFactory;
use pocketmine\Player; use pocketmine\Player;
use pocketmine\utils\Utils;
use function array_slice; use function array_slice;
use function count; use function count;
use function max; use function max;
@ -42,8 +41,8 @@ abstract class BaseInventory implements Inventory{
protected $slots = []; protected $slots = [];
/** @var Player[] */ /** @var Player[] */
protected $viewers = []; protected $viewers = [];
/** @var \Closure */ /** @var InventoryChangeListener[] */
protected $slotChangeListener; protected $listeners = [];
/** /**
* @param int $size * @param int $size
@ -121,6 +120,10 @@ abstract class BaseInventory implements Inventory{
} }
} }
foreach($this->listeners as $listener){
$listener->onContentChange($this);
}
if($send){ if($send){
$this->sendContents($this->getViewers()); $this->sendContents($this->getViewers());
} }
@ -138,10 +141,6 @@ abstract class BaseInventory implements Inventory{
$this->slots[$index] = $item->isNull() ? null : $item; $this->slots[$index] = $item->isNull() ? null : $item;
$this->onSlotChange($index, $oldItem, $send); $this->onSlotChange($index, $oldItem, $send);
if($this->slotChangeListener !== null){
($this->slotChangeListener)($this, $index);
}
return true; return true;
} }
@ -331,13 +330,7 @@ abstract class BaseInventory implements Inventory{
} }
public function clearAll(bool $send = true) : void{ public function clearAll(bool $send = true) : void{
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){ $this->setContents([], $send);
$this->clear($i, false);
}
if($send){
$this->sendContents($this->getViewers());
}
} }
public function swap(int $slot1, int $slot2) : void{ public function swap(int $slot1, int $slot2) : void{
@ -394,6 +387,9 @@ abstract class BaseInventory implements Inventory{
} }
protected function onSlotChange(int $index, Item $before, bool $send) : void{ protected function onSlotChange(int $index, Item $before, bool $send) : void{
foreach($this->listeners as $listener){
$listener->onSlotChange($this, $index);
}
if($send){ if($send){
$this->sendSlot($index, $this->getViewers()); $this->sendSlot($index, $this->getViewers());
} }
@ -430,14 +426,19 @@ abstract class BaseInventory implements Inventory{
return $slot >= 0 and $slot < $this->slots->getSize(); return $slot >= 0 and $slot < $this->slots->getSize();
} }
public function getSlotChangeListener() : ?\Closure{ public function addChangeListeners(InventoryChangeListener ...$listeners) : void{
return $this->slotChangeListener; foreach($listeners as $listener){
$this->listeners[spl_object_id($listener)] = $listener;
}
} }
public function setSlotChangeListener(?\Closure $eventProcessor) : void{ public function removeChangeListeners(InventoryChangeListener ...$listeners) : void{
if($eventProcessor !== null){ foreach($listeners as $listener){
Utils::validateCallableSignature(function(Inventory $inventory, int $slot) : void{}, $eventProcessor); unset($this->listeners[spl_object_id($listener)]);
} }
$this->slotChangeListener = $eventProcessor; }
public function getChangeListeners() : array{
return $this->listeners;
} }
} }

View File

@ -0,0 +1,65 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory;
use pocketmine\utils\Utils;
class CallbackInventoryChangeListener implements InventoryChangeListener{
/** @var \Closure|null */
private $onSlotChangeCallback;
/** @var \Closure|null */
private $onContentChangeCallback;
public function __construct(?\Closure $onSlotChange, ?\Closure $onContentChange){
if($onSlotChange !== null){
Utils::validateCallableSignature(function(Inventory $inventory, int $slot){}, $onSlotChange);
}
if($onContentChange !== null){
Utils::validateCallableSignature(function(Inventory $inventory){}, $onContentChange);
}
$this->onSlotChangeCallback = $onSlotChange;
$this->onContentChangeCallback = $onContentChange;
}
public static function onAnyChange(\Closure $onChange) : self{
return new self(
static function(Inventory $inventory, int $unused) use($onChange) : void{
$onChange($inventory);
},
static function(Inventory $inventory) use($onChange) : void{
$onChange($inventory);
}
);
}
public function onSlotChange(Inventory $inventory, int $slot) : void{
($this->onSlotChangeCallback)($inventory, $slot);
}
public function onContentChange(Inventory $inventory) : void{
($this->onContentChangeCallback)($inventory);
}
}

View File

@ -231,12 +231,17 @@ interface Inventory{
public function slotExists(int $slot) : bool; public function slotExists(int $slot) : bool;
/** /**
* @return null|\Closure * @param InventoryChangeListener ...$listeners
*/ */
public function getSlotChangeListener() : ?\Closure; public function addChangeListeners(InventoryChangeListener ...$listeners) : void;
/** /**
* @param \Closure|null $eventProcessor * @param InventoryChangeListener ...$listeners
*/ */
public function setSlotChangeListener(?\Closure $eventProcessor) : void; public function removeChangeListeners(InventoryChangeListener ...$listeners) : void;
/**
* @return InventoryChangeListener[]
*/
public function getChangeListeners() : array;
} }

View File

@ -0,0 +1,38 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\inventory;
/**
* Classes implementing this interface can be injected into inventories to receive notifications when content changes
* occur.
* @see CallbackInventoryChangeListener for a closure-based listener
* @see Inventory::addChangeListeners()
* @see Inventory::removeChangeListeners()
*/
interface InventoryChangeListener{
public function onSlotChange(Inventory $inventory, int $slot) : void;
public function onContentChange(Inventory $inventory) : void;
}

View File

@ -48,14 +48,14 @@ trait ContainerTrait{
$inventoryTag = $tag->getListTag(Container::TAG_ITEMS); $inventoryTag = $tag->getListTag(Container::TAG_ITEMS);
$inventory = $this->getRealInventory(); $inventory = $this->getRealInventory();
$slotChangeListener = $inventory->getSlotChangeListener(); $listeners = $inventory->getChangeListeners();
$inventory->setSlotChangeListener(null); //prevent any events being fired by initialization $inventory->removeChangeListeners(...$listeners); //prevent any events being fired by initialization
$inventory->clearAll(); $inventory->clearAll();
/** @var CompoundTag $itemNBT */ /** @var CompoundTag $itemNBT */
foreach($inventoryTag as $itemNBT){ foreach($inventoryTag as $itemNBT){
$inventory->setItem($itemNBT->getByte("Slot"), Item::nbtDeserialize($itemNBT)); $inventory->setItem($itemNBT->getByte("Slot"), Item::nbtDeserialize($itemNBT));
} }
$inventory->setSlotChangeListener($slotChangeListener); $inventory->addChangeListeners(...$listeners);
} }
if($tag->hasTag(Container::TAG_LOCK, StringTag::class)){ if($tag->hasTag(Container::TAG_LOCK, StringTag::class)){

View File

@ -26,6 +26,7 @@ namespace pocketmine\tile;
use pocketmine\block\Furnace as BlockFurnace; use pocketmine\block\Furnace as BlockFurnace;
use pocketmine\event\inventory\FurnaceBurnEvent; use pocketmine\event\inventory\FurnaceBurnEvent;
use pocketmine\event\inventory\FurnaceSmeltEvent; use pocketmine\event\inventory\FurnaceSmeltEvent;
use pocketmine\inventory\CallbackInventoryChangeListener;
use pocketmine\inventory\FurnaceInventory; use pocketmine\inventory\FurnaceInventory;
use pocketmine\inventory\FurnaceRecipe; use pocketmine\inventory\FurnaceRecipe;
use pocketmine\inventory\Inventory; use pocketmine\inventory\Inventory;
@ -60,9 +61,11 @@ class Furnace extends Spawnable implements InventoryHolder, Container, Nameable{
public function __construct(World $world, Vector3 $pos){ public function __construct(World $world, Vector3 $pos){
$this->inventory = new FurnaceInventory($this); $this->inventory = new FurnaceInventory($this);
$this->inventory->setSlotChangeListener(function(Inventory $inventory, int $slot) : void{ $this->inventory->addChangeListeners(CallbackInventoryChangeListener::onAnyChange(
$this->scheduleUpdate(); function(Inventory $unused) : void{
}); $this->scheduleUpdate();
})
);
parent::__construct($world, $pos); parent::__construct($world, $pos);
} }