Buffer slot and content syncing until the end of the tick

we may receive multiple requests in one tick (e.g. crafting in a batch)
This commit is contained in:
Dylan K. Taylor 2023-03-20 19:16:00 +00:00
parent e8085e22a0
commit ca6d51498f
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
4 changed files with 51 additions and 7 deletions

View File

@ -59,8 +59,11 @@ use pocketmine\network\PacketHandlingException;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\ObjectSet; use pocketmine\utils\ObjectSet;
use function array_keys;
use function array_search; use function array_search;
use function count;
use function get_class; use function get_class;
use function implode;
use function is_int; use function is_int;
use function max; use function max;
use function spl_object_id; use function spl_object_id;
@ -100,6 +103,8 @@ class InventoryManager{
private int $nextItemStackId = 1; private int $nextItemStackId = 1;
private ?int $currentItemStackRequestId = null; private ?int $currentItemStackRequestId = null;
private bool $fullSyncRequested = false;
public function __construct( public function __construct(
private Player $player, private Player $player,
private NetworkSession $session private NetworkSession $session
@ -400,7 +405,7 @@ class InventoryManager{
if($clientSideItem === null || !$clientSideItem->equals($currentItem)){ if($clientSideItem === null || !$clientSideItem->equals($currentItem)){
//no prediction or incorrect - do not associate this with the currently active itemstack request //no prediction or incorrect - do not associate this with the currently active itemstack request
$this->trackItemStack($inventory, $slot, $currentItem, null); $this->trackItemStack($inventory, $slot, $currentItem, null);
$this->syncSlot($inventory, $slot); $inventoryEntry->pendingSyncs[$slot] = $slot;
}else{ }else{
//correctly predicted - associate the change with the currently active itemstack request //correctly predicted - associate the change with the currently active itemstack request
$this->trackItemStack($inventory, $slot, $currentItem, $this->currentItemStackRequestId); $this->trackItemStack($inventory, $slot, $currentItem, $this->currentItemStackRequestId);
@ -452,7 +457,7 @@ class InventoryManager{
} }
$this->session->sendDataPacket(InventorySlotPacket::create($windowId, $netSlot, $itemStackWrapper)); $this->session->sendDataPacket(InventorySlotPacket::create($windowId, $netSlot, $itemStackWrapper));
} }
unset($entry->predictions[$slot]); unset($entry->predictions[$slot], $entry->pendingSyncs[$slot]);
} }
public function syncContents(Inventory $inventory) : void{ public function syncContents(Inventory $inventory) : void{
@ -464,6 +469,7 @@ class InventoryManager{
} }
if($windowId !== null){ if($windowId !== null){
$entry->predictions = []; $entry->predictions = [];
$entry->pendingSyncs = [];
$contents = []; $contents = [];
foreach($inventory->getContents(true) as $slot => $item){ foreach($inventory->getContents(true) as $slot => $item){
$itemStack = TypeConverter::getInstance()->coreItemStackToNet($item); $itemStack = TypeConverter::getInstance()->coreItemStackToNet($item);
@ -494,6 +500,10 @@ class InventoryManager{
} }
} }
public function requestSyncAll() : void{
$this->fullSyncRequested = true;
}
public function syncMismatchedPredictedSlotChanges() : void{ public function syncMismatchedPredictedSlotChanges() : void{
foreach($this->inventories as $entry){ foreach($this->inventories as $entry){
$inventory = $entry->inventory; $inventory = $entry->inventory;
@ -504,13 +514,33 @@ class InventoryManager{
//any prediction that still exists at this point is a slot that was predicted to change but didn't //any prediction that still exists at this point is a slot that was predicted to change but didn't
$this->session->getLogger()->debug("Detected prediction mismatch in inventory " . get_class($inventory) . "#" . spl_object_id($inventory) . " slot $slot"); $this->session->getLogger()->debug("Detected prediction mismatch in inventory " . get_class($inventory) . "#" . spl_object_id($inventory) . " slot $slot");
$this->syncSlot($inventory, $slot); $entry->pendingSyncs[$slot] = $slot;
} }
$entry->predictions = []; $entry->predictions = [];
} }
} }
public function flushPendingUpdates() : void{
if($this->fullSyncRequested){
$this->fullSyncRequested = false;
$this->session->getLogger()->debug("Full inventory sync requested, sending contents of " . count($this->inventories) . " inventories");
$this->syncAll();
}else{
foreach($this->inventories as $entry){
if(count($entry->pendingSyncs) === 0){
continue;
}
$inventory = $entry->inventory;
$this->session->getLogger()->debug("Syncing slots " . implode(", ", array_keys($entry->pendingSyncs)) . " in inventory " . get_class($inventory) . "#" . spl_object_id($inventory));
foreach($entry->pendingSyncs as $slot){
$this->syncSlot($inventory, $slot);
}
$entry->pendingSyncs = [];
}
}
}
public function syncData(Inventory $inventory, int $propertyId, int $value) : void{ public function syncData(Inventory $inventory, int $propertyId, int $value) : void{
$windowId = $this->getWindowId($inventory); $windowId = $this->getWindowId($inventory);
if($windowId !== null){ if($windowId !== null){

View File

@ -39,6 +39,12 @@ final class InventoryManagerEntry{
*/ */
public array $itemStackInfos = []; public array $itemStackInfos = [];
/**
* @var int[]
* @phpstan-var array<int, int>
*/
public array $pendingSyncs = [];
public function __construct( public function __construct(
public Inventory $inventory, public Inventory $inventory,
public ?ComplexInventoryMapEntry $complexSlotMap = null public ?ComplexInventoryMapEntry $complexSlotMap = null

View File

@ -1219,6 +1219,7 @@ class NetworkSession{
$attribute->markSynchronized(); $attribute->markSynchronized();
} }
} }
$this->invManager?->flushPendingUpdates();
$this->flushSendBuffer(); $this->flushSendBuffer();
} }

View File

@ -35,7 +35,8 @@ use pocketmine\event\player\PlayerEditBookEvent;
use pocketmine\inventory\transaction\action\DropItemAction; use pocketmine\inventory\transaction\action\DropItemAction;
use pocketmine\inventory\transaction\InventoryTransaction; use pocketmine\inventory\transaction\InventoryTransaction;
use pocketmine\inventory\transaction\TransactionBuilder; use pocketmine\inventory\transaction\TransactionBuilder;
use pocketmine\inventory\transaction\TransactionException; use pocketmine\inventory\transaction\TransactionCancelledException;
use pocketmine\inventory\transaction\TransactionValidationException;
use pocketmine\item\VanillaItems; use pocketmine\item\VanillaItems;
use pocketmine\item\WritableBook; use pocketmine\item\WritableBook;
use pocketmine\item\WritableBookPage; use pocketmine\item\WritableBookPage;
@ -335,7 +336,7 @@ class InGamePacketHandler extends PacketHandler{
$result = $this->handleNormalTransaction($packet->trData, $packet->requestId); $result = $this->handleNormalTransaction($packet->trData, $packet->requestId);
}elseif($packet->trData instanceof MismatchTransactionData){ }elseif($packet->trData instanceof MismatchTransactionData){
$this->session->getLogger()->debug("Mismatch transaction received"); $this->session->getLogger()->debug("Mismatch transaction received");
$this->inventoryManager->syncAll(); $this->inventoryManager->requestSyncAll();
$result = true; $result = true;
}elseif($packet->trData instanceof UseItemTransactionData){ }elseif($packet->trData instanceof UseItemTransactionData){
$result = $this->handleUseItemTransaction($packet->trData); $result = $this->handleUseItemTransaction($packet->trData);
@ -357,9 +358,14 @@ class InGamePacketHandler extends PacketHandler{
$this->inventoryManager->addTransactionPredictedSlotChanges($transaction); $this->inventoryManager->addTransactionPredictedSlotChanges($transaction);
try{ try{
$transaction->execute(); $transaction->execute();
}catch(TransactionException $e){ }catch(TransactionValidationException $e){
$this->inventoryManager->requestSyncAll();
$logger = $this->session->getLogger(); $logger = $this->session->getLogger();
$logger->debug("Failed to execute inventory transaction: " . $e->getMessage()); $logger->debug("Invalid inventory transaction $requestId: " . $e->getMessage());
return false;
}catch(TransactionCancelledException){
$this->session->getLogger()->debug("Inventory transaction $requestId cancelled by a plugin");
return false; return false;
}finally{ }finally{
@ -538,6 +544,7 @@ class InGamePacketHandler extends PacketHandler{
$result = false; $result = false;
$this->session->getLogger()->debug("ItemStackRequest #" . $request->getRequestId() . " failed: " . $e->getMessage()); $this->session->getLogger()->debug("ItemStackRequest #" . $request->getRequestId() . " failed: " . $e->getMessage());
$this->session->getLogger()->debug(implode("\n", Utils::printableExceptionInfo($e))); $this->session->getLogger()->debug(implode("\n", Utils::printableExceptionInfo($e)));
$this->inventoryManager->requestSyncAll();
} }
if(!$result){ if(!$result){