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\utils\AssumptionFailedError;
use pocketmine\utils\ObjectSet;
use function array_keys;
use function array_search;
use function count;
use function get_class;
use function implode;
use function is_int;
use function max;
use function spl_object_id;
@ -100,6 +103,8 @@ class InventoryManager{
private int $nextItemStackId = 1;
private ?int $currentItemStackRequestId = null;
private bool $fullSyncRequested = false;
public function __construct(
private Player $player,
private NetworkSession $session
@ -400,7 +405,7 @@ class InventoryManager{
if($clientSideItem === null || !$clientSideItem->equals($currentItem)){
//no prediction or incorrect - do not associate this with the currently active itemstack request
$this->trackItemStack($inventory, $slot, $currentItem, null);
$this->syncSlot($inventory, $slot);
$inventoryEntry->pendingSyncs[$slot] = $slot;
}else{
//correctly predicted - associate the change with the currently active itemstack request
$this->trackItemStack($inventory, $slot, $currentItem, $this->currentItemStackRequestId);
@ -452,7 +457,7 @@ class InventoryManager{
}
$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{
@ -464,6 +469,7 @@ class InventoryManager{
}
if($windowId !== null){
$entry->predictions = [];
$entry->pendingSyncs = [];
$contents = [];
foreach($inventory->getContents(true) as $slot => $item){
$itemStack = TypeConverter::getInstance()->coreItemStackToNet($item);
@ -494,6 +500,10 @@ class InventoryManager{
}
}
public function requestSyncAll() : void{
$this->fullSyncRequested = true;
}
public function syncMismatchedPredictedSlotChanges() : void{
foreach($this->inventories as $entry){
$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
$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 = [];
}
}
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{
$windowId = $this->getWindowId($inventory);
if($windowId !== null){

View File

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

View File

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

View File

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