mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-04-21 08:17:34 +00:00
Merge branch 'stable'
# Conflicts: # resources/vanilla # src/world/format/io/region/RegionGarbageMap.php # src/world/format/io/region/RegionLoader.php # tests/phpstan/configs/l7-baseline.neon # tests/phpunit/world/format/io/region/RegionLocationTableEntryTest.php
This commit is contained in:
commit
7d73630fb7
187
composer.lock
generated
187
composer.lock
generated
@ -1602,16 +1602,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-file-iterator",
|
||||
"version": "3.0.1",
|
||||
"version": "3.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/php-file-iterator.git",
|
||||
"reference": "4ac5b3e13df14829daa60a2eb4fdd2f2b7d33cf4"
|
||||
"reference": "eba15e538f2bb3fe018b7bbb47d2fe32d404bfd2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/4ac5b3e13df14829daa60a2eb4fdd2f2b7d33cf4",
|
||||
"reference": "4ac5b3e13df14829daa60a2eb4fdd2f2b7d33cf4",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/eba15e538f2bb3fe018b7bbb47d2fe32d404bfd2",
|
||||
"reference": "eba15e538f2bb3fe018b7bbb47d2fe32d404bfd2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1654,20 +1654,20 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-04-18T05:02:12+00:00"
|
||||
"time": "2020-06-15T12:54:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-invoker",
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/php-invoker.git",
|
||||
"reference": "7579d5a1ba7f3ac11c80004d205877911315ae7a"
|
||||
"reference": "62f696ad0d140e0e513e69eaafdebb674d622b4c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/7579d5a1ba7f3ac11c80004d205877911315ae7a",
|
||||
"reference": "7579d5a1ba7f3ac11c80004d205877911315ae7a",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/62f696ad0d140e0e513e69eaafdebb674d622b4c",
|
||||
"reference": "62f696ad0d140e0e513e69eaafdebb674d622b4c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1707,25 +1707,34 @@
|
||||
"keywords": [
|
||||
"process"
|
||||
],
|
||||
"time": "2020-02-07T06:06:11+00:00"
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sebastianbergmann",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-06-15T13:10:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-text-template",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/php-text-template.git",
|
||||
"reference": "526dc996cc0ebdfa428cd2dfccd79b7b53fee346"
|
||||
"reference": "0c69cbf965d5317ba33f24a352539f354a25db09"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/526dc996cc0ebdfa428cd2dfccd79b7b53fee346",
|
||||
"reference": "526dc996cc0ebdfa428cd2dfccd79b7b53fee346",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c69cbf965d5317ba33f24a352539f354a25db09",
|
||||
"reference": "0c69cbf965d5317ba33f24a352539f354a25db09",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
@ -1753,7 +1762,13 @@
|
||||
"keywords": [
|
||||
"template"
|
||||
],
|
||||
"time": "2020-02-01T07:43:44+00:00"
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sebastianbergmann",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-06-15T12:52:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-timer",
|
||||
@ -1867,16 +1882,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "9.2.2",
|
||||
"version": "9.2.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "8fd0d8f80029682da89516a554f4d5f5a030345c"
|
||||
"reference": "c1b1d62095ef78427f112a7a1c1502d4607e3c00"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8fd0d8f80029682da89516a554f4d5f5a030345c",
|
||||
"reference": "8fd0d8f80029682da89516a554f4d5f5a030345c",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c1b1d62095ef78427f112a7a1c1502d4607e3c00",
|
||||
"reference": "c1b1d62095ef78427f112a7a1c1502d4607e3c00",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1961,20 +1976,20 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-06-07T14:14:21+00:00"
|
||||
"time": "2020-06-15T10:51:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/code-unit",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/code-unit.git",
|
||||
"reference": "ac958085bc19fcd1d36425c781ef4cbb5b06e2a5"
|
||||
"reference": "d650ef9b1fece15ed4d6eaed6e6b469b7b81183a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/ac958085bc19fcd1d36425c781ef4cbb5b06e2a5",
|
||||
"reference": "ac958085bc19fcd1d36425c781ef4cbb5b06e2a5",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/d650ef9b1fece15ed4d6eaed6e6b469b7b81183a",
|
||||
"reference": "d650ef9b1fece15ed4d6eaed6e6b469b7b81183a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2013,20 +2028,20 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-04-30T05:58:10+00:00"
|
||||
"time": "2020-06-15T13:11:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/code-unit-reverse-lookup",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
|
||||
"reference": "5b5dbe0044085ac41df47e79d34911a15b96d82e"
|
||||
"reference": "c771130f0e8669104a4320b7101a81c2cc2963ef"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5b5dbe0044085ac41df47e79d34911a15b96d82e",
|
||||
"reference": "5b5dbe0044085ac41df47e79d34911a15b96d82e",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/c771130f0e8669104a4320b7101a81c2cc2963ef",
|
||||
"reference": "c771130f0e8669104a4320b7101a81c2cc2963ef",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2058,20 +2073,26 @@
|
||||
],
|
||||
"description": "Looks up which function or method a line of code belongs to",
|
||||
"homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
|
||||
"time": "2020-02-07T06:20:13+00:00"
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sebastianbergmann",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-06-15T12:56:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/comparator",
|
||||
"version": "4.0.0",
|
||||
"version": "4.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/comparator.git",
|
||||
"reference": "85b3435da967696ed618ff745f32be3ff4a2b8e8"
|
||||
"reference": "266d85ef789da8c41f06af4093c43e9798af2784"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/85b3435da967696ed618ff745f32be3ff4a2b8e8",
|
||||
"reference": "85b3435da967696ed618ff745f32be3ff4a2b8e8",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/266d85ef789da8c41f06af4093c43e9798af2784",
|
||||
"reference": "266d85ef789da8c41f06af4093c43e9798af2784",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2122,7 +2143,13 @@
|
||||
"compare",
|
||||
"equality"
|
||||
],
|
||||
"time": "2020-02-07T06:08:51+00:00"
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sebastianbergmann",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-06-15T15:04:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/diff",
|
||||
@ -2188,16 +2215,16 @@
|
||||
},
|
||||
{
|
||||
"name": "sebastian/environment",
|
||||
"version": "5.1.0",
|
||||
"version": "5.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/environment.git",
|
||||
"reference": "c753f04d68cd489b6973cf9b4e505e191af3b05c"
|
||||
"reference": "16eb0fa43e29c33d7f2117ed23072e26fc5ab34e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/c753f04d68cd489b6973cf9b4e505e191af3b05c",
|
||||
"reference": "c753f04d68cd489b6973cf9b4e505e191af3b05c",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/16eb0fa43e29c33d7f2117ed23072e26fc5ab34e",
|
||||
"reference": "16eb0fa43e29c33d7f2117ed23072e26fc5ab34e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2243,20 +2270,20 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-04-14T13:36:52+00:00"
|
||||
"time": "2020-06-15T13:00:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/exporter",
|
||||
"version": "4.0.0",
|
||||
"version": "4.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/exporter.git",
|
||||
"reference": "80c26562e964016538f832f305b2286e1ec29566"
|
||||
"reference": "d12fbca85da932d01d941b59e4b71a0d559db091"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/80c26562e964016538f832f305b2286e1ec29566",
|
||||
"reference": "80c26562e964016538f832f305b2286e1ec29566",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d12fbca85da932d01d941b59e4b71a0d559db091",
|
||||
"reference": "d12fbca85da932d01d941b59e4b71a0d559db091",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2310,7 +2337,13 @@
|
||||
"export",
|
||||
"exporter"
|
||||
],
|
||||
"time": "2020-02-07T06:10:52+00:00"
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sebastianbergmann",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-06-15T13:12:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/global-state",
|
||||
@ -2368,16 +2401,16 @@
|
||||
},
|
||||
{
|
||||
"name": "sebastian/object-enumerator",
|
||||
"version": "4.0.0",
|
||||
"version": "4.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/object-enumerator.git",
|
||||
"reference": "e67516b175550abad905dc952f43285957ef4363"
|
||||
"reference": "15f319d67c49fc55ebcdbffb3377433125588455"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67516b175550abad905dc952f43285957ef4363",
|
||||
"reference": "e67516b175550abad905dc952f43285957ef4363",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/15f319d67c49fc55ebcdbffb3377433125588455",
|
||||
"reference": "15f319d67c49fc55ebcdbffb3377433125588455",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2411,20 +2444,26 @@
|
||||
],
|
||||
"description": "Traverses array structures and object graphs to enumerate all referenced objects",
|
||||
"homepage": "https://github.com/sebastianbergmann/object-enumerator/",
|
||||
"time": "2020-02-07T06:12:23+00:00"
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sebastianbergmann",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-06-15T13:15:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/object-reflector",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/object-reflector.git",
|
||||
"reference": "f4fd0835cabb0d4a6546d9fe291e5740037aa1e7"
|
||||
"reference": "14e04b3c25b821cc0702d4837803fe497680b062"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/f4fd0835cabb0d4a6546d9fe291e5740037aa1e7",
|
||||
"reference": "f4fd0835cabb0d4a6546d9fe291e5740037aa1e7",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/14e04b3c25b821cc0702d4837803fe497680b062",
|
||||
"reference": "14e04b3c25b821cc0702d4837803fe497680b062",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2456,20 +2495,26 @@
|
||||
],
|
||||
"description": "Allows reflection of object attributes, including inherited and non-public ones",
|
||||
"homepage": "https://github.com/sebastianbergmann/object-reflector/",
|
||||
"time": "2020-02-07T06:19:40+00:00"
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sebastianbergmann",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-06-15T13:08:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/recursion-context",
|
||||
"version": "4.0.0",
|
||||
"version": "4.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/recursion-context.git",
|
||||
"reference": "cdd86616411fc3062368b720b0425de10bd3d579"
|
||||
"reference": "a32789e5f0157c10cf216ce6c5136db12a12b847"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cdd86616411fc3062368b720b0425de10bd3d579",
|
||||
"reference": "cdd86616411fc3062368b720b0425de10bd3d579",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/a32789e5f0157c10cf216ce6c5136db12a12b847",
|
||||
"reference": "a32789e5f0157c10cf216ce6c5136db12a12b847",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2509,20 +2554,26 @@
|
||||
],
|
||||
"description": "Provides functionality to recursively process PHP variables",
|
||||
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
|
||||
"time": "2020-02-07T06:18:20+00:00"
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sebastianbergmann",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-06-15T13:06:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/resource-operations",
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/resource-operations.git",
|
||||
"reference": "8c98bf0dfa1f9256d0468b9803a1e1df31b6fa98"
|
||||
"reference": "71421c1745788de4facae1b79af923650bd3ec15"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/8c98bf0dfa1f9256d0468b9803a1e1df31b6fa98",
|
||||
"reference": "8c98bf0dfa1f9256d0468b9803a1e1df31b6fa98",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/71421c1745788de4facae1b79af923650bd3ec15",
|
||||
"reference": "71421c1745788de4facae1b79af923650bd3ec15",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2554,7 +2605,13 @@
|
||||
],
|
||||
"description": "Provides a list of PHP built-in functions that operate on resources",
|
||||
"homepage": "https://www.github.com/sebastianbergmann/resource-operations",
|
||||
"time": "2020-02-07T06:13:02+00:00"
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sebastianbergmann",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-06-15T13:17:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/type",
|
||||
|
154
src/world/format/io/region/RegionGarbageMap.php
Normal file
154
src/world/format/io/region/RegionGarbageMap.php
Normal file
@ -0,0 +1,154 @@
|
||||
<?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\world\format\io\region;
|
||||
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use function end;
|
||||
use function ksort;
|
||||
use function time;
|
||||
use const SORT_NUMERIC;
|
||||
|
||||
final class RegionGarbageMap{
|
||||
|
||||
/** @var RegionLocationTableEntry[] */
|
||||
private $entries = [];
|
||||
/** @var bool */
|
||||
private $clean = false;
|
||||
|
||||
/**
|
||||
* @param RegionLocationTableEntry[] $entries
|
||||
*/
|
||||
public function __construct(array $entries){
|
||||
foreach($entries as $entry){
|
||||
$this->entries[$entry->getFirstSector()] = $entry;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RegionLocationTableEntry[]|null[] $locationTable
|
||||
*/
|
||||
public static function buildFromLocationTable(array $locationTable) : self{
|
||||
/** @var RegionLocationTableEntry[] $usedMap */
|
||||
$usedMap = [];
|
||||
foreach($locationTable as $entry){
|
||||
if($entry === null){
|
||||
continue;
|
||||
}
|
||||
if(isset($usedMap[$entry->getFirstSector()])){
|
||||
throw new AssumptionFailedError("Overlapping entries detected");
|
||||
}
|
||||
$usedMap[$entry->getFirstSector()] = $entry;
|
||||
}
|
||||
|
||||
ksort($usedMap, SORT_NUMERIC);
|
||||
|
||||
/** @var RegionLocationTableEntry[] $garbageMap */
|
||||
$garbageMap = [];
|
||||
|
||||
/** @var RegionLocationTableEntry|null $prevEntry */
|
||||
$prevEntry = null;
|
||||
foreach($usedMap as $firstSector => $entry){
|
||||
$expectedStart = ($prevEntry !== null ? $prevEntry->getLastSector() + 1 : RegionLoader::FIRST_SECTOR);
|
||||
$actualStart = $entry->getFirstSector();
|
||||
if($expectedStart < $actualStart){
|
||||
//found a gap in the table
|
||||
$garbageMap[$expectedStart] = new RegionLocationTableEntry($expectedStart, $actualStart - $expectedStart, 0);
|
||||
}
|
||||
$prevEntry = $entry;
|
||||
}
|
||||
|
||||
return new self($garbageMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RegionLocationTableEntry[]
|
||||
* @phpstan-return array<int, RegionLocationTableEntry>
|
||||
*/
|
||||
public function getArray() : array{
|
||||
if(!$this->clean){
|
||||
ksort($this->entries, SORT_NUMERIC);
|
||||
|
||||
/** @var int|null $prevIndex */
|
||||
$prevIndex = null;
|
||||
foreach($this->entries as $k => $entry){
|
||||
if($prevIndex !== null and $this->entries[$prevIndex]->getLastSector() + 1 === $entry->getFirstSector()){
|
||||
//this SHOULD overwrite the previous index and not appear at the end
|
||||
$this->entries[$prevIndex] = new RegionLocationTableEntry(
|
||||
$this->entries[$prevIndex]->getFirstSector(),
|
||||
$this->entries[$prevIndex]->getSectorCount() + $entry->getSectorCount(),
|
||||
0
|
||||
);
|
||||
unset($this->entries[$k]);
|
||||
}else{
|
||||
$prevIndex = $k;
|
||||
}
|
||||
}
|
||||
$this->clean = true;
|
||||
}
|
||||
return $this->entries;
|
||||
}
|
||||
|
||||
public function add(RegionLocationTableEntry $entry) : void{
|
||||
if(isset($this->entries[$k = $entry->getFirstSector()])){
|
||||
throw new \InvalidArgumentException("Overlapping entry starting at " . $k);
|
||||
}
|
||||
$this->entries[$k] = $entry;
|
||||
$this->clean = false;
|
||||
}
|
||||
|
||||
public function remove(RegionLocationTableEntry $entry) : void{
|
||||
if(isset($this->entries[$k = $entry->getFirstSector()])){
|
||||
//removal doesn't affect ordering and shouldn't affect fragmentation
|
||||
unset($this->entries[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
public function end() : ?RegionLocationTableEntry{
|
||||
$array = $this->getArray();
|
||||
$end = end($array);
|
||||
return $end !== false ? $end : null;
|
||||
}
|
||||
|
||||
public function allocate(int $newSize) : ?RegionLocationTableEntry{
|
||||
foreach($this->getArray() as $start => $candidate){
|
||||
$candidateSize = $candidate->getSectorCount();
|
||||
if($candidateSize < $newSize){
|
||||
continue;
|
||||
}
|
||||
|
||||
$newLocation = new RegionLocationTableEntry($candidate->getFirstSector(), $newSize, time());
|
||||
$this->remove($candidate);
|
||||
|
||||
if($candidateSize > $newSize){ //we're not using the whole area, just take part of it
|
||||
$newGarbageStart = $candidate->getFirstSector() + $newSize;
|
||||
$newGarbageSize = $candidateSize - $newSize;
|
||||
$this->add(new RegionLocationTableEntry($newGarbageStart, $newGarbageSize, 0));
|
||||
}
|
||||
return $newLocation;
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@ use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\world\format\ChunkException;
|
||||
use pocketmine\world\format\io\exception\CorruptedChunkException;
|
||||
use function assert;
|
||||
use function ceil;
|
||||
use function chr;
|
||||
use function fclose;
|
||||
@ -39,9 +40,11 @@ use function fseek;
|
||||
use function ftruncate;
|
||||
use function fwrite;
|
||||
use function is_resource;
|
||||
use function ksort;
|
||||
use function max;
|
||||
use function ord;
|
||||
use function str_pad;
|
||||
use function str_repeat;
|
||||
use function stream_set_read_buffer;
|
||||
use function stream_set_write_buffer;
|
||||
use function strlen;
|
||||
@ -49,6 +52,7 @@ use function substr;
|
||||
use function time;
|
||||
use function touch;
|
||||
use function unpack;
|
||||
use const SORT_NUMERIC;
|
||||
use const STR_PAD_RIGHT;
|
||||
|
||||
class RegionLoader{
|
||||
@ -58,7 +62,7 @@ class RegionLoader{
|
||||
private const MAX_SECTOR_LENGTH = 255 << 12; //255 sectors (~0.996 MiB)
|
||||
private const REGION_HEADER_LENGTH = 8192; //4096 location table + 4096 timestamps
|
||||
|
||||
private const FIRST_SECTOR = 2; //location table occupies 0 and 1
|
||||
public const FIRST_SECTOR = 2; //location table occupies 0 and 1
|
||||
|
||||
/** @var int */
|
||||
public static $COMPRESSION_LEVEL = 7;
|
||||
@ -69,13 +73,16 @@ class RegionLoader{
|
||||
protected $filePointer;
|
||||
/** @var int */
|
||||
protected $nextSector = self::FIRST_SECTOR;
|
||||
/** @var RegionLocationTableEntry[] */
|
||||
/** @var RegionLocationTableEntry[]|null[] */
|
||||
protected $locationTable = [];
|
||||
/** @var RegionGarbageMap */
|
||||
protected $garbageTable;
|
||||
/** @var int */
|
||||
public $lastUsed = 0;
|
||||
|
||||
public function __construct(string $filePath){
|
||||
$this->filePath = $filePath;
|
||||
$this->garbageTable = new RegionGarbageMap([]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -110,7 +117,7 @@ class RegionLoader{
|
||||
}
|
||||
|
||||
protected function isChunkGenerated(int $index) : bool{
|
||||
return !$this->locationTable[$index]->isNull();
|
||||
return $this->locationTable[$index] !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -122,7 +129,7 @@ class RegionLoader{
|
||||
|
||||
$this->lastUsed = time();
|
||||
|
||||
if(!$this->isChunkGenerated($index)){
|
||||
if($this->locationTable[$index] === null){
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -182,19 +189,50 @@ class RegionLoader{
|
||||
|
||||
$newSize = (int) ceil(($length + 4) / 4096);
|
||||
$index = self::getChunkOffset($x, $z);
|
||||
$offset = $this->locationTable[$index]->getFirstSector();
|
||||
|
||||
if($this->locationTable[$index]->getSectorCount() < $newSize){
|
||||
$offset = $this->nextSector;
|
||||
/*
|
||||
* look for an unused area big enough to hold this data
|
||||
* this is corruption-resistant (it leaves the old data intact if a failure occurs when writing new data), and
|
||||
* also allows the file to become more compact across consecutive writes without introducing a dedicated garbage
|
||||
* collection mechanism.
|
||||
*/
|
||||
$newLocation = $this->garbageTable->allocate($newSize);
|
||||
|
||||
/* if no gaps big enough were found, append to the end of the file instead */
|
||||
if($newLocation === null){
|
||||
$newLocation = new RegionLocationTableEntry($this->nextSector, $newSize, time());
|
||||
$this->bumpNextFreeSector($newLocation);
|
||||
}
|
||||
|
||||
$this->locationTable[$index] = new RegionLocationTableEntry($offset, $newSize, time());
|
||||
$this->bumpNextFreeSector($this->locationTable[$index]);
|
||||
|
||||
fseek($this->filePointer, $offset << 12);
|
||||
/* write the chunk data into the chosen location */
|
||||
fseek($this->filePointer, $newLocation->getFirstSector() << 12);
|
||||
fwrite($this->filePointer, str_pad(Binary::writeInt($length) . chr(self::COMPRESSION_ZLIB) . $chunkData, $newSize << 12, "\x00", STR_PAD_RIGHT));
|
||||
|
||||
/*
|
||||
* update the file header - we do this after writing the main data, so that if a failure occurs while writing,
|
||||
* the header will still point to the old (intact) copy of the chunk, instead of a potentially broken new
|
||||
* version of the file (e.g. partially written).
|
||||
*/
|
||||
$oldLocation = $this->locationTable[$index];
|
||||
$this->locationTable[$index] = $newLocation;
|
||||
$this->writeLocationIndex($index);
|
||||
|
||||
if($oldLocation !== null){
|
||||
/* release the area containing the old copy to the garbage pool */
|
||||
$this->garbageTable->add($oldLocation);
|
||||
|
||||
$endGarbage = $this->garbageTable->end();
|
||||
$nextSector = $this->nextSector;
|
||||
for(; $endGarbage !== null and $endGarbage->getLastSector() + 1 === $nextSector; $endGarbage = $this->garbageTable->end()){
|
||||
$nextSector = $endGarbage->getFirstSector();
|
||||
$this->garbageTable->remove($endGarbage);
|
||||
}
|
||||
|
||||
if($nextSector !== $this->nextSector){
|
||||
$this->nextSector = $nextSector;
|
||||
ftruncate($this->filePointer, $this->nextSector << 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -202,7 +240,7 @@ class RegionLoader{
|
||||
*/
|
||||
public function removeChunk(int $x, int $z) : void{
|
||||
$index = self::getChunkOffset($x, $z);
|
||||
$this->locationTable[$index] = new RegionLocationTableEntry(0, 0, 0);
|
||||
$this->locationTable[$index] = null;
|
||||
$this->writeLocationIndex($index);
|
||||
}
|
||||
|
||||
@ -241,8 +279,8 @@ class RegionLoader{
|
||||
fseek($this->filePointer, 0);
|
||||
|
||||
$headerRaw = fread($this->filePointer, self::REGION_HEADER_LENGTH);
|
||||
if(($len = strlen($headerRaw)) !== self::REGION_HEADER_LENGTH){
|
||||
throw new CorruptedRegionException("Invalid region file header, expected " . self::REGION_HEADER_LENGTH . " bytes, got " . $len . " bytes");
|
||||
if($headerRaw === false or strlen($headerRaw) !== self::REGION_HEADER_LENGTH){
|
||||
throw new CorruptedRegionException("Corrupted region header (unexpected end of file)");
|
||||
}
|
||||
|
||||
$data = unpack("N*", $headerRaw);
|
||||
@ -250,18 +288,23 @@ class RegionLoader{
|
||||
for($i = 0; $i < 1024; ++$i){
|
||||
$index = $data[$i + 1];
|
||||
$offset = $index >> 8;
|
||||
$sectorCount = $index & 0xff;
|
||||
$timestamp = $data[$i + 1025];
|
||||
|
||||
if($offset === 0){
|
||||
$this->locationTable[$i] = new RegionLocationTableEntry(0, 0, 0);
|
||||
if($offset === 0 or $sectorCount === 0){
|
||||
$this->locationTable[$i] = null;
|
||||
}elseif($offset >= self::FIRST_SECTOR){
|
||||
$this->bumpNextFreeSector($this->locationTable[$i] = new RegionLocationTableEntry($offset, $sectorCount, $timestamp));
|
||||
}else{
|
||||
$this->locationTable[$i] = new RegionLocationTableEntry($offset, $index & 0xff, $timestamp);
|
||||
$this->bumpNextFreeSector($this->locationTable[$i]);
|
||||
self::getChunkCoords($i, $chunkXX, $chunkZZ);
|
||||
throw new CorruptedRegionException("Invalid region header entry for x=$chunkXX z=$chunkZZ, offset overlaps with header");
|
||||
}
|
||||
}
|
||||
|
||||
$this->checkLocationTableValidity();
|
||||
|
||||
$this->garbageTable = RegionGarbageMap::buildFromLocationTable($this->locationTable);
|
||||
|
||||
fseek($this->filePointer, 0);
|
||||
}
|
||||
|
||||
@ -274,7 +317,7 @@ class RegionLoader{
|
||||
|
||||
for($i = 0; $i < 1024; ++$i){
|
||||
$entry = $this->locationTable[$i];
|
||||
if($entry->isNull()){
|
||||
if($entry === null){
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -294,25 +337,78 @@ class RegionLoader{
|
||||
}
|
||||
$usedOffsets[$offset] = $i;
|
||||
}
|
||||
ksort($usedOffsets, SORT_NUMERIC);
|
||||
$prevLocationIndex = null;
|
||||
foreach($usedOffsets as $startOffset => $locationTableIndex){
|
||||
if($this->locationTable[$locationTableIndex] === null){
|
||||
continue;
|
||||
}
|
||||
if($prevLocationIndex !== null){
|
||||
assert($this->locationTable[$prevLocationIndex] !== null);
|
||||
if($this->locationTable[$locationTableIndex]->overlaps($this->locationTable[$prevLocationIndex])){
|
||||
self::getChunkCoords($locationTableIndex, $chunkXX, $chunkZZ);
|
||||
self::getChunkCoords($prevLocationIndex, $prevChunkXX, $prevChunkZZ);
|
||||
throw new CorruptedRegionException("Overlapping chunks detected in region header (chunk1: x=$chunkXX,z=$chunkZZ, chunk2: x=$prevChunkXX,z=$prevChunkZZ)");
|
||||
}
|
||||
}
|
||||
$prevLocationIndex = $locationTableIndex;
|
||||
}
|
||||
}
|
||||
|
||||
protected function writeLocationIndex(int $index) : void{
|
||||
$entry = $this->locationTable[$index];
|
||||
fseek($this->filePointer, $index << 2);
|
||||
fwrite($this->filePointer, Binary::writeInt(($this->locationTable[$index]->getFirstSector() << 8) | $this->locationTable[$index]->getSectorCount()), 4);
|
||||
fwrite($this->filePointer, Binary::writeInt($entry !== null ? ($entry->getFirstSector() << 8) | $entry->getSectorCount() : 0), 4);
|
||||
fseek($this->filePointer, 4096 + ($index << 2));
|
||||
fwrite($this->filePointer, Binary::writeInt($this->locationTable[$index]->getTimestamp()), 4);
|
||||
fwrite($this->filePointer, Binary::writeInt($entry !== null ? $entry->getTimestamp() : 0), 4);
|
||||
}
|
||||
|
||||
protected function createBlank() : void{
|
||||
fseek($this->filePointer, 0);
|
||||
ftruncate($this->filePointer, 8192); // this fills the file with the null byte
|
||||
for($i = 0; $i < 1024; ++$i){
|
||||
$this->locationTable[$i] = new RegionLocationTableEntry(0, 0, 0);
|
||||
$this->locationTable[$i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
private function bumpNextFreeSector(RegionLocationTableEntry $entry) : void{
|
||||
$this->nextSector = max($this->nextSector, $entry->getLastSector()) + 1;
|
||||
$this->nextSector = max($this->nextSector, $entry->getLastSector() + 1);
|
||||
}
|
||||
|
||||
public function generateSectorMap(string $usedChar, string $freeChar) : string{
|
||||
$result = str_repeat($freeChar, $this->nextSector);
|
||||
for($i = 0; $i < self::FIRST_SECTOR; ++$i){
|
||||
$result[$i] = $usedChar;
|
||||
}
|
||||
foreach($this->locationTable as $locationTableEntry){
|
||||
if($locationTableEntry === null){
|
||||
continue;
|
||||
}
|
||||
foreach($locationTableEntry->getUsedSectors() as $sectorIndex){
|
||||
if($sectorIndex >= strlen($result)){
|
||||
throw new AssumptionFailedError("This should never happen...");
|
||||
}
|
||||
if($result[$sectorIndex] === $usedChar){
|
||||
throw new AssumptionFailedError("Overlap detected");
|
||||
}
|
||||
$result[$sectorIndex] = $usedChar;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a float between 0 and 1 indicating what fraction of the file is currently unused space.
|
||||
*/
|
||||
public function getProportionUnusedSpace() : float{
|
||||
$size = $this->nextSector;
|
||||
$used = self::FIRST_SECTOR; //header is always allocated
|
||||
foreach($this->locationTable as $entry){
|
||||
if($entry !== null){
|
||||
$used += $entry->getSectorCount();
|
||||
}
|
||||
}
|
||||
return 1 - ($used / $size);
|
||||
}
|
||||
|
||||
public function getFilePath() : string{
|
||||
|
@ -38,12 +38,12 @@ class RegionLocationTableEntry{
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct(int $firstSector, int $sectorCount, int $timestamp){
|
||||
if($firstSector < 0){
|
||||
if($firstSector < 0 or $firstSector >= 2 ** 24){
|
||||
throw new \InvalidArgumentException("Start sector must be positive, got $firstSector");
|
||||
}
|
||||
$this->firstSector = $firstSector;
|
||||
if($sectorCount < 0 or $sectorCount > 255){
|
||||
throw new \InvalidArgumentException("Sector count must be in range 0...255, got $sectorCount");
|
||||
if($sectorCount < 1){
|
||||
throw new \InvalidArgumentException("Sector count must be positive, got $sectorCount");
|
||||
}
|
||||
$this->sectorCount = $sectorCount;
|
||||
$this->timestamp = $timestamp;
|
||||
@ -73,7 +73,16 @@ class RegionLocationTableEntry{
|
||||
return $this->timestamp;
|
||||
}
|
||||
|
||||
public function isNull() : bool{
|
||||
return $this->firstSector === 0 or $this->sectorCount === 0;
|
||||
public function overlaps(RegionLocationTableEntry $other) : bool{
|
||||
$overlapCheck = static function(RegionLocationTableEntry $entry1, RegionLocationTableEntry $entry2) : bool{
|
||||
$entry1Last = $entry1->getLastSector();
|
||||
$entry2Last = $entry2->getLastSector();
|
||||
|
||||
return (
|
||||
($entry2->firstSector >= $entry1->firstSector and $entry2->firstSector <= $entry1Last) or
|
||||
($entry2Last >= $entry1->firstSector and $entry2Last <= $entry1Last)
|
||||
);
|
||||
};
|
||||
return $overlapCheck($this, $other) or $overlapCheck($other, $this);
|
||||
}
|
||||
}
|
||||
|
@ -1040,16 +1040,6 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/world/format/io/region/RegionLoader.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$string of function strlen expects string, string\\|false given\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/world/format/io/region/RegionLoader.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$data of function unpack expects string, string\\|false given\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/world/format/io/region/RegionLoader.php
|
||||
|
||||
-
|
||||
message: "#^Argument of an invalid type array\\<int, string\\>\\|false supplied for foreach, only iterables are supported\\.$#"
|
||||
count: 1
|
||||
|
@ -0,0 +1,52 @@
|
||||
<?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\world\format\io\region;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class RegionLocationTableEntryTest extends TestCase{
|
||||
|
||||
/**
|
||||
* @phpstan-return \Generator<int, array{RegionLocationTableEntry, RegionLocationTableEntry, bool}, void, void>
|
||||
*/
|
||||
public function overlapDataProvider() : \Generator{
|
||||
yield [new RegionLocationTableEntry(2, 1, 0), new RegionLocationTableEntry(2, 1, 0), true];
|
||||
yield [new RegionLocationTableEntry(2, 1, 0), new RegionLocationTableEntry(3, 1, 0), false];
|
||||
yield [new RegionLocationTableEntry(2, 2, 0), new RegionLocationTableEntry(3, 2, 0), true];
|
||||
yield [new RegionLocationTableEntry(2, 2, 0), new RegionLocationTableEntry(4, 2, 0), false];
|
||||
yield [new RegionLocationTableEntry(2, 2, 0), new RegionLocationTableEntry(2, 1, 0), true];
|
||||
yield [new RegionLocationTableEntry(2, 4, 0), new RegionLocationTableEntry(3, 1, 0), true];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider overlapDataProvider
|
||||
*/
|
||||
public function testOverlap(RegionLocationTableEntry $entry1, RegionLocationTableEntry $entry2, bool $overlaps) : void{
|
||||
$stringify = function(RegionLocationTableEntry $entry) : string{
|
||||
return sprintf("entry first=%d last=%d size=%d", $entry->getFirstSector(), $entry->getLastSector(), $entry->getSectorCount());
|
||||
};
|
||||
self::assertSame($overlaps, $entry1->overlaps($entry2), $stringify($entry1) . " expected to " . ($overlaps ? "overlap" : "not overlap") . " with " . $stringify($entry2));
|
||||
self::assertSame($overlaps, $entry2->overlaps($entry1), $stringify($entry2) . " expected to " . ($overlaps ? "overlap" : "not overlap") . " with " . $stringify($entry1));
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user