├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── behavior-bug-or-plugin-incompatibility.yml │ ├── config.yml │ ├── disclaimer.txt │ ├── feature-request.yml │ ├── performance-problem.yml │ └── server-crash-or-stacktrace.yml └── workflows │ ├── build.yml │ └── upstream.yml ├── .gitignore ├── DEVELOPING_A_MULTITHREAD_PLUGIN.md ├── HOW_IT_WORKS.md ├── LICENSE.txt ├── README.md ├── SHREDDEDPAPER_YAML.md ├── assets ├── chunk-diagram.png ├── multipaper-diagram.jpg └── region-diagram.png ├── build-data └── dev-imports.txt ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── patches ├── api │ ├── 0001-Pufferfish-API.patch │ ├── 0002-Add-isFoliaSupported.patch │ ├── 0003-Add-various-API-for-Folia-plugin-compatibility.patch │ └── 0004-ShreddedPaper-branding.patch ├── removed │ └── 0026-Tick-chunks-fully-async.patch └── server │ ├── 0001-Pufferfish.patch │ ├── 0002-ShreddedPaper-build-changes.patch │ ├── 0003-ShreddedPaper-config-file.patch │ ├── 0004-Add-SimpleStampedLock.patch │ ├── 0005-Add-multithreading-region-scheduler.patch │ ├── 0006-Multithread-chunk-ticking.patch │ ├── 0007-Thread-safe-random.patch │ ├── 0008-Thread-safe-NeigbhorUpdater.patch │ ├── 0009-Multithread-entity-ticking.patch │ ├── 0010-Add-task-scheduling-API.patch │ ├── 0011-Player.patch │ ├── 0012-Level-ticks.patch │ ├── 0013-Custom-spawners.patch │ ├── 0014-Chunk-unloading.patch │ ├── 0015-mpmap-debug-command.patch │ ├── 0016-EntityLookup-accessibleEntities.patch │ ├── 0017-World-gen.patch │ ├── 0018-Block-entities.patch │ ├── 0019-Multithreaded-WeakSeqLock.patch │ ├── 0020-Teleportation.patch │ ├── 0021-TrackedEntity.patch │ ├── 0022-Thread-safe-navigatingMobs.patch │ ├── 0023-entityMap-thread-safety.patch │ ├── 0024-Misc-threadsafety.patch │ ├── 0025-NearbyPlayers.patch │ ├── 0026-Run-unsupported-plugins-in-sync.patch │ ├── 0027-Thread-safe-AreaMap.patch │ ├── 0028-Threaded-chunk-changes-broadcasting.patch │ ├── 0029-ThreadLocals-for-block-state-capturing.patch │ ├── 0030-Portal.patch │ ├── 0031-Ender-dragon-fight.patch │ ├── 0032-End-Gateways.patch │ ├── 0033-Block-events.patch │ ├── 0034-Add-various-API-for-Folia-plugin-compatibility.patch │ ├── 0035-allow-unsupported-plugins-to-modify-chunks-via-globa.patch │ ├── 0036-Entity-retirement-debug-log.patch │ ├── 0037-Raids.patch │ ├── 0038-Thread-safe-alternate-redstone-handler.patch │ ├── 0039-Thread-safe-redstoneUpdateInfos.patch │ ├── 0040-Thread-safe-chunksBeingWorkedOn.patch │ ├── 0041-kill-command.patch │ ├── 0042-Purpur-teleportIfOutsideBorder-use-teleportAsync.patch │ ├── 0043-Ensure-worlds-are-loaded-on-the-global-scheduler.patch │ ├── 0044-Plugin-not-folia-supported-warning.patch │ ├── 0045-Thread-safe-scoreboards.patch │ ├── 0046-Optimization-entity-activation-check-frequency.patch │ ├── 0047-handlingTick.patch │ ├── 0048-Synchronize-playersSentChunkTo.patch │ ├── 0049-PotentialCalculator.patch │ ├── 0050-Remove-firework-rocket-if-entity-teleported-away.patch │ ├── 0051-Bukkit-API-thread-checks.patch │ ├── 0052-give-command.patch │ ├── 0053-clear-command.patch │ ├── 0054-Send-ping-packet-later.patch │ ├── 0055-Moving-into-another-region.patch │ └── 0056-Don-t-allow-actions-outside-of-our-region.patch └── settings.gradle.kts /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | *.sh text eol=lf 4 | gradlew text eol=lf 5 | *.bat text eol=crlf 6 | 7 | *.jar binary 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/behavior-bug-or-plugin-incompatibility.yml: -------------------------------------------------------------------------------- 1 | name: Behavior Bug or Plugin Incompatibility 2 | description: Report issues with plugin incompatbility or other behavior related issues. 3 | labels: [ ] 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: Expected behavior 8 | description: What you expected to see. 9 | validations: 10 | required: true 11 | 12 | - type: textarea 13 | attributes: 14 | label: Observed/Actual behavior 15 | description: What you actually saw. 16 | validations: 17 | required: true 18 | 19 | - type: textarea 20 | attributes: 21 | label: Steps/models to reproduce 22 | description: This may include a build schematic, a video, or detailed instructions to help reconstruct the issue. 23 | validations: 24 | required: true 25 | 26 | - type: textarea 27 | attributes: 28 | label: Plugin and Datapack List 29 | description: | 30 | All plugins and datapacks running on your server. 31 | To list plugins, run `/plugins`. For datapacks, run `/datapack list`. 32 | validations: 33 | required: true 34 | 35 | - type: textarea 36 | attributes: 37 | label: ShreddedPaper version 38 | description: | 39 | Run `/version` on your server and **paste** the full, unmodified output here. 40 | "latest" is *not* a version; we require the output of `/version` so we can adequately track down the issue. 41 | Additionally, do NOT provide a screenshot, you MUST paste the entire output. 42 |
43 | Example 44 | 45 | ``` 46 | > version 47 | [20:34:42 INFO]: This server is running Paper version git-Paper-540 (MC: 1.16.5) (Implementing API version 1.16.5-R0.1-SNAPSHOT) 48 | [20:34:42 INFO]: Checking version, please wait... 49 | [20:34:42 INFO]: Previous version: git-Paper-538 (MC: 1.16.5) 50 | [20:34:42 INFO]: You are running the latest version 51 | ``` 52 | 53 |
54 | validations: 55 | required: true 56 | 57 | - type: textarea 58 | attributes: 59 | label: Other 60 | description: | 61 | Please include other helpful information below. 62 | The more information we receive, the quicker and more effective we can be at finding the solution to the issue. 63 | validations: 64 | required: false 65 | 66 | - type: markdown 67 | attributes: 68 | value: | 69 | Before submitting this issue, please ensure the following: 70 | 71 | 1. You are running the latest version of ShreddedPaper from [our downloads page](https://shreddedpaper.io/download.html). 72 | 2. You searched for and ensured there isn't already an open issue regarding this. 73 | 3. Your version of Minecraft is supported by ShreddedPaper. 74 | 4. The bug is not reproducible on Paper. 75 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: ShreddedPaper Discord 4 | url: https://discord.gg/qaj2g3rjt2 5 | about: If you are having issues with timings or have other minor issues, come ask us on our Discord server! 6 | #- name: Dupe / exploits Report 7 | # url: https://discord.gg/qaj2g3rjt2 8 | # about: | 9 | # Due to GitHub not currently allowing private issues, exploit / dupes reports are currently handled via our Discord. 10 | # To report an exploit, see the #paper-exploit-report channel. 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/disclaimer.txt: -------------------------------------------------------------------------------- 1 | Some of issues templates are modified version of PaperMC's issue templates 2 | which can be found here: https://github.com/PaperMC/Paper/tree/master/.github/ISSUE_TEMPLATE -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest an idea for ShreddedPaper 3 | labels: [] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thank you for filling out a feature request for ShreddedPaper! Please be as detailed as possible so that we may consider and review the request easier. 9 | We ask that you search all the issues to avoid a duplicate feature request. If one exists, please reply if you have anything to add. 10 | Before requesting a new feature, please make sure you are using the latest version and that the feature you are requesting is not already in ShreddedPaper. 11 | 12 | - type: textarea 13 | attributes: 14 | label: Is your feature request related to a problem? 15 | description: Please give some context for this request. Why do you want it added? 16 | validations: 17 | required: true 18 | 19 | - type: textarea 20 | attributes: 21 | label: Describe the solution you'd like. 22 | description: A clear and concise description of what you want. 23 | validations: 24 | required: true 25 | 26 | - type: textarea 27 | attributes: 28 | label: Describe alternatives you've considered. 29 | description: List any alternatives you might have tried to get the feature you want. 30 | validations: 31 | required: true 32 | 33 | - type: textarea 34 | attributes: 35 | label: Other 36 | description: Add any other context or screenshots about the feature request below. 37 | validations: 38 | required: false 39 | 40 | - type: markdown 41 | attributes: 42 | value: | 43 | Before submitting this feature request, please search our issue tracker to ensure your feature has not 44 | already been requested. 45 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/performance-problem.yml: -------------------------------------------------------------------------------- 1 | name: Performance Problem 2 | description: Report performance related problems or other areas of concern 3 | labels: [ ] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Before creating an issue regarding server performance, please consider reaching out for support in the 9 | `#questions` or `#general` channels of [our Discord](https://discord.gg/qaj2g3rjt2)! 10 | 11 | - type: input 12 | attributes: 13 | label: Timings or Profile link 14 | description: We ask that all timings/profiles are a link, not a screenshot. Screenshots inhibit our ability to figure out the real cause of the issue. 15 | placeholder: "Example: https://timings.aikar.co/?id=6b48586fbbdd48e585ca0ebb07c59dd0" 16 | validations: 17 | required: true 18 | 19 | - type: textarea 20 | attributes: 21 | label: Description of issue 22 | description: If applicable, please describe your issue. 23 | validations: 24 | required: false 25 | 26 | - type: textarea 27 | attributes: 28 | label: Plugin and Datapack List 29 | description: | 30 | All plugins and datapacks running on your server. 31 | To list plugins, run `/plugins`. For datapacks, run `/datapack list`. 32 | validations: 33 | required: true 34 | 35 | - type: textarea 36 | attributes: 37 | label: Server config files 38 | description: We need bukkit.yml, spigot.yml, paper-global.yml, paper-world-defaults.yml, shreddedpaper.yml and server.properties. If you use per-world Paper configs, make sure to include them. You can paste it below or use a paste site like https://paste.gg. 39 | value: | 40 | ``` 41 | Paste configs or paste.gg link here! 42 | ``` 43 | placeholder: Please don't remove the backticks; it makes your issue a lot harder to read! 44 | validations: 45 | required: true 46 | 47 | - type: textarea 48 | attributes: 49 | label: ShreddedPaper version 50 | description: | 51 | Run `/version` on your server and **paste** the full, unmodified output here. 52 | "latest" is *not* a version; we require the output of `/version` so we can adequately track down the issue. 53 | Additionally, do NOT provide a screenshot, you MUST paste the entire output. 54 |
55 | Example 56 | 57 | ``` 58 | > version 59 | [20:34:42 INFO]: This server is running Paper version git-Paper-540 (MC: 1.16.5) (Implementing API version 1.16.5-R0.1-SNAPSHOT) 60 | [20:34:42 INFO]: Checking version, please wait... 61 | [20:34:42 INFO]: Previous version: git-Paper-538 (MC: 1.16.5) 62 | [20:34:42 INFO]: You are running the latest version 63 | ``` 64 | 65 |
66 | validations: 67 | required: true 68 | 69 | - type: textarea 70 | attributes: 71 | label: Other 72 | description: | 73 | Please include other helpful links below. 74 | The more information we receive, the quicker and more effective we can be at finding the solution to the issue. 75 | validations: 76 | required: false 77 | - type: markdown 78 | attributes: 79 | value: | 80 | Before submitting this issue, please ensure the following: 81 | 82 | 1. You are running the latest version of ShreddedPaper from [our downloads page](https://shreddedpaper.io/download.html). 83 | 2. You searched for and ensured there isn't already an open issue regarding this. 84 | 3. Your version of Minecraft is supported by ShreddedPaper. 85 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/server-crash-or-stacktrace.yml: -------------------------------------------------------------------------------- 1 | name: Server crash or Stacktrace 2 | description: Report server crashes or scary stacktraces 3 | labels: [ ] 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: Stack trace 8 | description: | 9 | We need all of the stack trace! Do not cut off parts of it. Please do not use attachments. 10 | If you prefer, you can use a paste site like https://paste.gg. 11 | value: | 12 | ``` 13 | paste your stack trace or a paste.gg link here! 14 | ``` 15 | placeholder: Please don't remove the backticks; it makes your issue a lot harder to read! 16 | validations: 17 | required: true 18 | 19 | - type: textarea 20 | attributes: 21 | label: Plugin and Datapack List 22 | description: | 23 | All plugins and datapacks running on your server. 24 | To list plugins, run `/plugins`. For datapacks, run `/datapack list`. 25 | validations: 26 | required: true 27 | 28 | - type: textarea 29 | attributes: 30 | label: Actions to reproduce (if known) 31 | description: This may include a build schematic, a video, or detailed instructions to help reconstruct the issue. Anything helps! 32 | validations: 33 | required: false 34 | 35 | - type: textarea 36 | attributes: 37 | label: ShreddedPaper version 38 | description: | 39 | Run `/version` on your server and **paste** the full, unmodified output here. 40 | "latest" is *not* a version; we require the output of `/version` so we can adequately track down the issue. 41 | Additionally, do NOT provide a screenshot, you MUST paste the entire output. 42 |
43 | Example 44 | 45 | ``` 46 | > version 47 | [20:34:42 INFO]: This server is running Paper version git-Paper-540 (MC: 1.16.5) (Implementing API version 1.16.5-R0.1-SNAPSHOT) 48 | [20:34:42 INFO]: Checking version, please wait... 49 | [20:34:42 INFO]: Previous version: git-Paper-538 (MC: 1.16.5) 50 | [20:34:42 INFO]: You are running the latest version 51 | ``` 52 | 53 |
54 | validations: 55 | required: true 56 | 57 | - type: textarea 58 | attributes: 59 | label: Other 60 | description: | 61 | Please include other helpful information below, if any. 62 | The more information we receive, the quicker and more effective we can be at finding the solution to the issue. 63 | validations: 64 | required: false 65 | 66 | - type: markdown 67 | attributes: 68 | value: | 69 | Before submitting this issue, please ensure the following: 70 | 71 | 1. You are running the latest version of ShreddedPaper from [our downloads page](https://shreddedpaper.io/download.html). 72 | 2. Your version of Minecraft is supported by ShreddedPaper. 73 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Generate Jars 2 | on: [ push, pull_request ] 3 | jobs: 4 | paperclip: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Checkout Git Repository 8 | uses: actions/checkout@v4 9 | - name: Set up JDK 10 | uses: actions/setup-java@v4 11 | with: 12 | java-version: '21' 13 | cache: 'gradle' 14 | distribution: 'temurin' 15 | - name: Configure Git User Details 16 | run: git config --global user.email "actions@github.com" && git config --global user.name "Github Actions" 17 | - name: Apply Patches 18 | run: ./gradlew applyPatches --stacktrace 19 | - name: Build Paperclip jar 20 | run: ./gradlew createMojmapPaperclipJar --stacktrace 21 | - name: Upload Paperclip jar 22 | uses: actions/upload-artifact@v4 23 | with: 24 | name: shreddedpaper.jar 25 | path: build/libs/shreddedpaper-paperclip-*-mojmap.jar 26 | -------------------------------------------------------------------------------- /.github/workflows/upstream.yml: -------------------------------------------------------------------------------- 1 | name: Update Upstream 2 | 3 | on: 4 | schedule: 5 | - cron: '0 15 * * *' # Once a day at 5pm GMT+2 6 | workflow_dispatch: # on button click 7 | 8 | jobs: 9 | upstream: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout Git Repository 15 | uses: actions/checkout@v4 16 | - name: Set up JDK 17 | uses: actions/setup-java@v4 18 | with: 19 | java-version: '21' 20 | cache: 'gradle' 21 | distribution: 'temurin' 22 | - name: Configure Git User Details 23 | run: git config --global user.email "actions@github.com" && git config --global user.name "Github Actions" 24 | - name: Update Upstream Commit Hash 25 | run: ./gradlew purpurRefLatest --stacktrace 26 | - name: Apply Patches 27 | run: ./gradlew applyPatches --stacktrace 28 | - name: Build Paperclip jar 29 | run: ./gradlew jar createMojmapPaperclipJar --stacktrace 30 | - name: Rebuild Patches 31 | run: ./gradlew rebuildPatches --stacktrace 32 | - name: Upload Paperclip jar 33 | uses: actions/upload-artifact@v4 34 | with: 35 | name: shreddedpaper.jar 36 | path: build/libs/shreddedpaper-paperclip-*-mojmap.jar 37 | - name: Create Pull Request 38 | uses: peter-evans/create-pull-request@v6 39 | with: 40 | commit-message: Update Upstream 41 | branch: update-upstream 42 | delete-branch: true 43 | title: 'Update Upstream' 44 | body: '' 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | build/ 3 | 4 | # Eclipse stuff 5 | .classpath 6 | .project 7 | .settings/ 8 | 9 | # VSCode stuff 10 | .vscode/ 11 | 12 | # netbeans 13 | nbproject/ 14 | nbactions.xml 15 | 16 | # we use maven! 17 | build.xml 18 | 19 | # maven 20 | target/ 21 | dependency-reduced-pom.xml 22 | 23 | # vim 24 | .*.sw[a-p] 25 | 26 | # various other potential build files 27 | build/ 28 | bin/ 29 | dist/ 30 | manifest.mf 31 | 32 | # Mac filesystem dust 33 | .DS_Store/ 34 | .DS_Store 35 | 36 | # intellij 37 | *.iml 38 | *.ipr 39 | *.iws 40 | .idea/ 41 | out/ 42 | 43 | # Linux temp files 44 | *~ 45 | 46 | # other stuff 47 | run/ 48 | 49 | ShreddedPaper-Server 50 | ShreddedPaper-API 51 | paper-api-generator 52 | 53 | *.sh 54 | shreddedpaper.jar 55 | 56 | !gradle/wrapper/gradle-wrapper.jar 57 | -------------------------------------------------------------------------------- /DEVELOPING_A_MULTITHREAD_PLUGIN.md: -------------------------------------------------------------------------------- 1 | # Developing a multi-thread plugin 2 | 3 | When developing a plugin for a multi-threaded server, you must be careful of 4 | multiple things: 5 | 6 | ## 1. Updating data 7 | 8 | Be careful of multiple threads updating data at the same time. This can cause 9 | a race-condition between threads, meaning the data will not be updated 10 | correctly. 11 | 12 | For example, given the command: 13 | 14 | ```java 15 | final HashMap playerMoney = new HashMap<>(); 16 | 17 | // Called via /givemoney PureGero 100 18 | public void giveMoneyCommand(UUID player, int amount) { 19 | int currentMoney = playerMoney.get(player); 20 | int newMoney = currentMoney + amount; 21 | playerMoney.put(player, newMoney); 22 | } 23 | ``` 24 | 25 | Commands can be called from multiple threads at the same time. If two threads 26 | try to update the same player's money at the same time, the following will 27 | happen: 28 | 29 | 1. Thread 1 reads the current money as 100. 30 | 2. Thread 2 reads the current money as 100. 31 | 3. Thread 1 adds 100 to the current money, making it 200. 32 | 4. Thread 2 adds 100 to the current money, making it 200. 33 | 5. Thread 1 writes the new money as 200. 34 | 6. Thread 2 writes the new money as 200. 35 | 7. The player's money is now 200, not 300. Money has been lost. 36 | 37 | To solve this, you can use a synchronized block around the code, which will 38 | ensure the code is only called on one thread at a time. 39 | 40 | ```java 41 | final HashMap playerMoney = new HashMap<>(); 42 | 43 | // Called via /givemoney PureGero 100 44 | public void giveMoneyCommand(UUID player, int amount) { 45 | synchronized (playerMoney) { 46 | // Only one thread will run this code at a time while in a 47 | // synchronized block for a given object 48 | int currentMoney = playerMoney.get(player); 49 | int newMoney = currentMoney + amount; 50 | playerMoney.put(player, newMoney); 51 | } 52 | } 53 | ``` 54 | 55 | You can take this a step further by using a ConcurrentHashMap. A HashMap is not 56 | thread-safe, meaning data can be lost if two threads update any data in it at 57 | the same time. However, a ConcurrentHashMap is thread-safe. 58 | 59 | In addition, ConcurrentHashMap lets you lock a certain value while updating it, 60 | meaning you don't have to lock the entire HashMap, but can instead lock one key. 61 | 62 | ```java 63 | final ConcurrentHashMap playerMoney = new ConcurrentHashMap<>(); 64 | 65 | // Called via /givemoney PureGero 100 66 | public void giveMoneyCommand(UUID player, int amount) { 67 | playerMoney.compute(player, (key, currentValue) -> { 68 | // This locks the 'player' key in the map, meaning only one thread can 69 | // update it at a time 70 | if (currentValue == null) currentValue = 0; // Values default to null 71 | 72 | return currentValue + amount; 73 | }); 74 | } 75 | ``` 76 | 77 | ## 2. Reading data 78 | 79 | Be careful of one thread reading data while it is being updated by another 80 | thread. This can cause the thread to read incorrect data. 81 | 82 | For example: 83 | 84 | ```java 85 | List players = new ArrayList<>(); 86 | 87 | for (UUID player : players) { 88 | // If another thread removes a player from the list, this will throw an 89 | // IndexOutOfBoundsException 90 | } 91 | 92 | for (int i = 0; i < players.size(); i++) { 93 | // If another thread removes a player to the list, this may skip a player 94 | // as the indecies will shift during the iteration. 95 | 96 | UUID player = players.get(i); 97 | // `player` may be null, or an IndexOutOfBoundsException may be thrown if 98 | // a player was removed from the list by another thread 99 | } 100 | ``` 101 | 102 | ## 3. Chunk threads 103 | 104 | Code must be executed on the chunk's thread. You can no longer rely on a main 105 | thread. 106 | 107 | For example, for a command the updates a block: 108 | 109 | ```java 110 | // A player at 0,0,0 wants to update a far-away block at 1000,0,0 111 | // This block will certainly be on a different thread, so we must use the 112 | // block's thread instead. 113 | public void updateBlockCommand(Player player, Location blockLocation) { 114 | Bukkit.getRegionScheduler().run(plugin, blockLocation, task -> { 115 | // This code block will be run on the chunk at blockLocation's thread 116 | blockLocation.getBlock().setType(Material.DIAMOND_BLOCK); 117 | }); 118 | 119 | } 120 | ``` 121 | 122 | Another example, for a command that updates an entity: 123 | 124 | ```java 125 | // A player at 0,0,0 wants to update a far-away entity at 1000,0,0 126 | // This entity will certainly be on a different thread, so we must use the 127 | // entity's thread instead. 128 | public void updateEntityCommand(Player player, Entity entity) { 129 | entity.getScheduler().run(plugin, task -> { 130 | // This code block will be run on the entity's chunk's thread, even if 131 | // the entity moves to another chunk! 132 | Bukkit.broadcastMessage("Hello, world!"); 133 | }, null); 134 | } 135 | ``` 136 | 137 | Note that these two examples use Paper's Scheduler API, so you must reference 138 | the paper api rather than the spigot api in your dependencies. 139 | 140 | If you want to continue supporting Spigot and other Bukkit variants, check out 141 | [MultiLib](https://github.com/MultiPaper/MultiLib#shreddedpaper--folia-methods) 142 | which includes fallback mechanics for Bukkit servers. 143 | 144 | ## 4. Use entity.teleportAsync 145 | 146 | This one's easy enough. 147 | 148 | Do this: 149 | 150 | ```java 151 | entity.teleportAsync(location); 152 | ``` 153 | 154 | Not this: 155 | 156 | ```java 157 | entity.teleport(location); // Do not do this 158 | ``` -------------------------------------------------------------------------------- /HOW_IT_WORKS.md: -------------------------------------------------------------------------------- 1 | # How it works 2 | 3 | ## Fundamental principle 4 | 5 | Chunks are split across multiple threads to increase the speed of each tick. 6 | 7 | However, if two threads are trying to access the same chunk at the same time, 8 | everything breaks. 9 | 10 | Thus, we need to lock the chunk when we tick it, along with any nearby chunks 11 | we might need to access (eg redstone going into the neighbouring chunk). 12 | 13 | ![A diagram visualising the locking principle when ticking a chunk](assets/chunk-diagram.png) 14 | 15 | ## Optimising with regions 16 | 17 | A minecraft server can have thousands of chunks loaded at once, and locking each 18 | one individually when ticking them 20 times per second would be very slow. 19 | 20 | To combat this, we split the chunks into regions, and lock the region instead of 21 | each individual chunk. 22 | 23 | If we set the size of the region to be the same as the locking radius, we will 24 | only have to lock the directly neighbouring regions in a 3x3 grid around the 25 | region of chunks that we wish to tick, which makes the locking process much 26 | faster. 27 | 28 | ![A diagram visualising how the regions are set up](assets/region-diagram.png) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ShreddedPaper 2 | 3 | [![Discord](https://img.shields.io/discord/937309618743427113.svg?color=738ad6&label=Join%20the%20Discord%20server&logo=discord&logoColor=ffffff)](https://discord.gg/dN3WCZkSRV) 4 | 5 | **ShreddedPaper is in public beta.** Most features work for most players most of 6 | the time, however things can occasionally break. 7 | 8 | 1.20.6 [Purpur](https://github.com/PurpurMC/Purpur) fork that brings vertical scaling to Minecraft. 9 | 10 | ShreddedPaper: 11 | 12 | - Allows multiple threads to work together to run a single world 13 | - When ticking a chunk on one thread, all other chunks in a certain radius 14 | are locked so that only this thread has access to them, preventing any 15 | race conditions between threads. 16 | 17 | See [HOW_IT_WORKS.md](HOW_IT_WORKS.md) for more information on how ShreddedPaper 18 | works. 19 | 20 | ### Developing a plugin for a multi-threaded server 21 | 22 | In summary, a plugin must be careful of: 23 | 24 | - Different threads updating certain data at the same time. 25 | - One thread reading data while it is being updated by another thread. 26 | - Code is to be executed on the chunk's thread, not simply the main thread. 27 | 28 | [See here for a more detailed tutorial](DEVELOPING_A_MULTITHREAD_PLUGIN.md) 29 | 30 | If your plugin already has support for Folia it is highly likely that it will already work with ShreddedPaper without any changes. 31 | If you have a Folia check similar to the following: 32 | ```java 33 | try { 34 | Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); 35 | return true; 36 | } catch (ClassNotFoundException e) { 37 | return false; 38 | } 39 | ``` 40 | You should remove it and instead check of the existence of Paper's region api: 41 | ```java 42 | try { 43 | Bukkit.class.getMethod("getRegionScheduler"); 44 | return true; 45 | } catch (NoSuchMethodException e) { 46 | return false; 47 | } 48 | ``` 49 | 50 | ### Using the ShreddedPaper API as a dependency 51 | 52 | [![Clojars Project](https://img.shields.io/clojars/v/com.github.puregero/shreddedpaper-api.svg)](https://clojars.org/com.github.puregero/shreddedpaper-api) 53 | 54 | Add the following into your build.gradle: 55 | 56 | ``` 57 | repositories { 58 | maven { 59 | url "https://repo.clojars.org/" 60 | } 61 | } 62 | 63 | dependencies { 64 | compile "com.github.puregero:shreddedpaper-api:1.20.6-R0.1-SNAPSHOT" 65 | } 66 | ``` 67 | 68 | Or in your pom.xml: 69 | 70 | ``` 71 | 72 | 73 | clojars 74 | https://repo.clojars.org/ 75 | 76 | 77 | 78 | 79 | com.github.puregero 80 | shreddedpaper-api 81 | 1.20.6-R0.1-SNAPSHOT 82 | 83 | 84 | ``` 85 | 86 | ## Building 87 | Requirements: 88 | - You need `git` installed, with a configured user name and email. 89 | On windows you need to run from git bash. 90 | - You need `jdk` 21+ installed to compile (and `jre` 21+ to run) 91 | 92 | Build instructions: 93 | 1. Patch paper with: `./gradlew applyPatches` 94 | 2. Build the shreddedpaper jars with: `./gradlew shadowjar createReobfPaperclipJar` 95 | 3. Get the shreddedpaper jar from `build/libs` 96 | 97 | ## Publishing to maven local 98 | Publish to your local maven repository with: `./gradlew publishToMavenLocal` 99 | 100 | Note for mac users: The latest macOS version includes an incompatible version of 101 | diff and you'll need to install a compatible one. Use `brew install diffutils` 102 | to install it, and then reopen the terminal window. 103 | 104 | If `diff --version` returns the following, it is incompatible and will not work: 105 | ``` 106 | Apple diff (based on FreeBSD diff) 107 | ``` 108 | 109 | ### Licensing 110 | 111 | All code is licensed under [GPLv3](LICENSE.txt). 112 | 113 | ### Acknowledgements 114 | 115 | ShreddedPaper uses PaperMC's paperweight framework found 116 | [here](https://github.com/PaperMC/paperweight). -------------------------------------------------------------------------------- /SHREDDEDPAPER_YAML.md: -------------------------------------------------------------------------------- 1 | # shreddedpaper.yml 2 | 3 | ```yaml 4 | 5 | # Multithreading settings 6 | multithreading: 7 | 8 | # The number of threads to use for ticking chunks. `-1` defaults to the number 9 | # of available processors subtract one, or 1, whichever is larger. 10 | # Set to `1` to essentially disable multi-threading. 11 | thread-count: -1 12 | 13 | # The size of a region in chunks. Chunks are grouped into regions when 14 | # ticking, and neighbouring regions are locked so any changes into the 15 | # neighbouring region do not step on other threads. 16 | # The larger the value, the more performant the server will be, but the more 17 | # likely the work is not evenly distributed between threads. Too small, and 18 | # threads may accidentally try accessing chunks of another thread. 19 | # A minimum of 8 chunks is recommended, as lightning rods have the largest 20 | # search radius at 8 chunks. 21 | # Must be a power of 2. 22 | region-size: 8 23 | 24 | # Whether to run plugins' code without 'folia-supported: true' in sync. This 25 | # will give unsupported plugins a better chance of working, but is not 26 | # guaranteed. If false, or if the plugin is supported, all the plugin's code 27 | # will run asynchronously in the ticking region worker threads. 28 | run-unsupported-plugins-in-sync: true 29 | 30 | # Can unsupported plugins modify blocks/entities/etc from the global scheduler 31 | # This will be false if there are multiple servers as tasks in the global 32 | # scheduler do not have the required region locks. These locks aren't needed 33 | # for single server instances. 34 | allow-unsupported-plugins-to-modify-chunks-via-global-scheduler: true 35 | 36 | # ShreddedPaper's optimizations settings 37 | optimizations: 38 | 39 | # Check entity activation range less often. Spigot does this every tick 40 | # unnecessarily. Set to '0' to disable. 41 | entity-activation-check-frequency: 20 42 | 43 | ``` -------------------------------------------------------------------------------- /assets/chunk-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiPaper/ShreddedPaper/2c77569e4d4986699602faadd8658f98d60675db/assets/chunk-diagram.png -------------------------------------------------------------------------------- /assets/multipaper-diagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiPaper/ShreddedPaper/2c77569e4d4986699602faadd8658f98d60675db/assets/multipaper-diagram.jpg -------------------------------------------------------------------------------- /assets/region-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiPaper/ShreddedPaper/2c77569e4d4986699602faadd8658f98d60675db/assets/region-diagram.png -------------------------------------------------------------------------------- /build-data/dev-imports.txt: -------------------------------------------------------------------------------- 1 | # You can use this file to import files from minecraft libraries into the project 2 | # format: 3 | # 4 | # both fully qualified and a file based syntax are accepted for : 5 | # authlib com/mojang/authlib/yggdrasil/YggdrasilGameProfileRepository.java 6 | # datafixerupper com.mojang.datafixers.DataFixerBuilder 7 | # datafixerupper com/mojang/datafixers/util/Either.java 8 | # To import classes from the vanilla Minecraft jar use `minecraft` as the artifactId: 9 | # minecraft net.minecraft.world.level.entity.LevelEntityGetterAdapter 10 | # minecraft net/minecraft/world/level/entity/LevelEntityGetter.java 11 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import io.papermc.paperweight.util.* 2 | import io.papermc.paperweight.util.constants.* 3 | 4 | plugins { 5 | java 6 | `maven-publish` 7 | id("io.github.goooler.shadow") version "8.1.7" 8 | id("io.papermc.paperweight.patcher") version "1.7.1" 9 | } 10 | 11 | repositories { 12 | mavenCentral() 13 | maven("https://repo.papermc.io/repository/maven-public/") { 14 | content { onlyForConfigurations(PAPERCLIP_CONFIG) } 15 | } 16 | } 17 | 18 | dependencies { 19 | remapper("net.fabricmc:tiny-remapper:0.10.1:fat") 20 | decompiler("org.vineflower:vineflower:1.10.1") 21 | paperclip("io.papermc:paperclip:3.0.3") 22 | } 23 | 24 | allprojects { 25 | apply(plugin = "java") 26 | apply(plugin = "maven-publish") 27 | 28 | java { 29 | toolchain { 30 | languageVersion.set(JavaLanguageVersion.of(21)) 31 | } 32 | } 33 | } 34 | 35 | subprojects { 36 | tasks.withType { 37 | options.encoding = Charsets.UTF_8.name() 38 | options.release.set(21) 39 | } 40 | tasks.withType { 41 | options.encoding = Charsets.UTF_8.name() 42 | } 43 | tasks.withType { 44 | filteringCharset = Charsets.UTF_8.name() 45 | } 46 | 47 | repositories { 48 | mavenCentral() 49 | maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") // TODO - Adventure snapshot 50 | maven("https://repo.papermc.io/repository/maven-public/") 51 | maven("https://oss.sonatype.org/content/groups/public/") 52 | maven("https://ci.emc.gs/nexus/content/groups/aikar/") 53 | maven("https://repo.aikar.co/content/groups/aikar") 54 | maven("https://repo.md-5.net/content/repositories/releases/") 55 | maven("https://hub.spigotmc.org/nexus/content/groups/public/") 56 | maven("https://jitpack.io") 57 | } 58 | } 59 | 60 | paperweight { 61 | serverProject.set(project(":shreddedpaper-server")) 62 | 63 | remapRepo.set("https://repo.papermc.io/repository/maven-public/") 64 | decompileRepo.set("https://repo.papermc.io/repository/maven-public/") 65 | 66 | useStandardUpstream("Purpur") { 67 | url.set(github("PurpurMC", "Purpur")) 68 | ref.set(providers.gradleProperty("purpurRef")) 69 | 70 | withStandardPatcher { 71 | apiSourceDirPath.set("Purpur-API") 72 | apiPatchDir.set(layout.projectDirectory.dir("patches/api")) 73 | apiOutputDir.set(layout.projectDirectory.dir("ShreddedPaper-API")) 74 | 75 | serverSourceDirPath.set("Purpur-Server") 76 | serverPatchDir.set(layout.projectDirectory.dir("patches/server")) 77 | serverOutputDir.set(layout.projectDirectory.dir("ShreddedPaper-Server")) 78 | } 79 | 80 | patchTasks.register("generatedApi") { 81 | isBareDirectory = true 82 | upstreamDirPath = "paper-api-generator/generated" 83 | patchDir = layout.projectDirectory.dir("patches/generated-api") 84 | outputDir = layout.projectDirectory.dir("paper-api-generator/generated") 85 | } 86 | } 87 | 88 | tasks.register("purpurRefLatest") { 89 | // Update the paperRef in gradle.properties to be the latest commit 90 | val tempDir = layout.cacheDir("purpurRefLatest"); 91 | val file = "gradle.properties"; 92 | 93 | doFirst { 94 | data class GithubCommit( 95 | val sha: String 96 | ) 97 | 98 | val purpurLatestCommitJson = layout.cache.resolve("purpurLatestCommit.json"); 99 | download.get().download("https://api.github.com/repos/PurpurMC/Purpur/commits/ver/1.20.6", purpurLatestCommitJson); 100 | val purpurLatestCommit = gson.fromJson(purpurLatestCommitJson)["sha"].asString; 101 | 102 | copy { 103 | from(file) 104 | into(tempDir) 105 | filter { line: String -> 106 | line.replace("purpurRef = .*".toRegex(), "purpurRef = $purpurLatestCommit") 107 | } 108 | } 109 | } 110 | 111 | doLast { 112 | copy { 113 | from(tempDir.file("gradle.properties")) 114 | into(project.file(file).parent) 115 | } 116 | } 117 | } 118 | } 119 | 120 | tasks.generateDevelopmentBundle { 121 | apiCoordinates.set("io.multipaper.shreddedpaper:ShreddedPaper-API") 122 | libraryRepositories.set( 123 | listOf( 124 | "https://repo.maven.apache.org/maven2/", 125 | "https://repo.papermc.io/repository/maven-public/", 126 | "https://jitpack.io" 127 | ) 128 | ) 129 | } 130 | publishing { 131 | publications.create("devBundle") { 132 | artifact(tasks.generateDevelopmentBundle) { 133 | artifactId = "dev-bundle" 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | group = io.multipaper.shreddedpaper 2 | version = 1.20.6-R0.1-SNAPSHOT 3 | mcVersion = 1.20.6 4 | 5 | purpurRef = 0d6766e21d08f8348c666a460cb145fdc3a45ed0 6 | 7 | org.gradle.caching=true 8 | org.gradle.parallel=true 9 | org.gradle.vfs.watch=false 10 | 11 | org.gradle.jvmargs="-Xmx4G" -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiPaper/ShreddedPaper/2c77569e4d4986699602faadd8658f98d60675db/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | # Collect all arguments for the java command; 201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 202 | # shell script including quotes and variable substitutions, so put them in 203 | # double quotes to make sure that they get re-expanded; and 204 | # * put everything else in single quotes, so that it's not re-expanded. 205 | 206 | set -- \ 207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 208 | -classpath "$CLASSPATH" \ 209 | org.gradle.wrapper.GradleWrapperMain \ 210 | "$@" 211 | 212 | # Stop when "xargs" is not available. 213 | if ! command -v xargs >/dev/null 2>&1 214 | then 215 | die "xargs is not available" 216 | fi 217 | 218 | # Use "xargs" to parse quoted args. 219 | # 220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 221 | # 222 | # In Bash we could simply go: 223 | # 224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 225 | # set -- "${ARGS[@]}" "$@" 226 | # 227 | # but POSIX shell has neither arrays nor command substitution, so instead we 228 | # post-process each arg (as a line of input to sed) to backslash-escape any 229 | # character that might be a shell metacharacter, then use eval to reverse 230 | # that process (while maintaining the separation between arguments), and wrap 231 | # the whole thing up as a single "set" statement. 232 | # 233 | # This will of course break if any of these variables contains a newline or 234 | # an unmatched quote. 235 | # 236 | 237 | eval "set -- $( 238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 239 | xargs -n1 | 240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 241 | tr '\n' ' ' 242 | )" '"$@"' 243 | 244 | exec "$JAVACMD" "$@" 245 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /patches/api/0002-Add-isFoliaSupported.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Sat, 25 May 2024 18:44:13 +0900 4 | Subject: [PATCH] Add isFoliaSupported 5 | 6 | 7 | diff --git a/src/main/java/org/bukkit/plugin/PluginDescriptionFile.java b/src/main/java/org/bukkit/plugin/PluginDescriptionFile.java 8 | index a857e46fa6f0c212db93193e1fdd8b0ea9c33c38..26efead73ac9a1d0d33a4137738cf3ab958af271 100644 9 | --- a/src/main/java/org/bukkit/plugin/PluginDescriptionFile.java 10 | +++ b/src/main/java/org/bukkit/plugin/PluginDescriptionFile.java 11 | @@ -260,6 +260,7 @@ public final class PluginDescriptionFile implements io.papermc.paper.plugin.conf 12 | private Set awareness = ImmutableSet.of(); 13 | private String apiVersion = null; 14 | private List libraries = ImmutableList.of(); 15 | + private String foliaSupported = null; // ShreddedPaper 16 | // Paper start - plugin loader api 17 | private String paperPluginLoader; 18 | @org.jetbrains.annotations.ApiStatus.Internal @org.jetbrains.annotations.Nullable 19 | @@ -1057,6 +1058,11 @@ public final class PluginDescriptionFile implements io.papermc.paper.plugin.conf 20 | return libraries; 21 | } 22 | 23 | + // ShreddedPaper start 24 | + public boolean isFoliaSupported() { 25 | + return "true".equalsIgnoreCase(foliaSupported); 26 | + } 27 | + 28 | /** 29 | * @return unused 30 | * @deprecated unused 31 | @@ -1258,6 +1264,12 @@ public final class PluginDescriptionFile implements io.papermc.paper.plugin.conf 32 | } 33 | // Paper end - plugin loader api 34 | 35 | + // ShreddedPaper start 36 | + if (map.get("folia-supported") != null) { 37 | + foliaSupported = map.get("folia-supported").toString(); 38 | + } 39 | + // ShreddedPaper end 40 | + 41 | try { 42 | lazyPermissions = (Map) map.get("permissions"); 43 | } catch (ClassCastException ex) { 44 | @@ -1336,6 +1348,12 @@ public final class PluginDescriptionFile implements io.papermc.paper.plugin.conf 45 | map.put("libraries", libraries); 46 | } 47 | 48 | + // ShreddedPaper start 49 | + if (foliaSupported != null) { 50 | + map.put("folia-supported", foliaSupported); 51 | + } 52 | + // ShreddedPaper end 53 | + 54 | if (classLoaderOf != null) { 55 | map.put("class-loader-of", classLoaderOf); 56 | } 57 | -------------------------------------------------------------------------------- /patches/api/0003-Add-various-API-for-Folia-plugin-compatibility.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: ProdPreva1l 3 | Date: Wed, 5 Jun 2024 20:44:50 +1000 4 | Subject: [PATCH] Add various API for Folia plugin compatibility 5 | 6 | 7 | diff --git a/src/main/java/io/papermc/paper/threadedregions/RegionizedServerInitEvent.java b/src/main/java/io/papermc/paper/threadedregions/RegionizedServerInitEvent.java 8 | new file mode 100644 9 | index 0000000000000000000000000000000000000000..d991df9523676ab7ba90f45ecae3ea9dac5b55d6 10 | --- /dev/null 11 | +++ b/src/main/java/io/papermc/paper/threadedregions/RegionizedServerInitEvent.java 12 | @@ -0,0 +1,24 @@ 13 | +package io.papermc.paper.threadedregions; 14 | + 15 | +import org.bukkit.event.Event; 16 | +import org.bukkit.event.HandlerList; 17 | +import org.jetbrains.annotations.ApiStatus; 18 | +import org.jetbrains.annotations.NotNull; 19 | + 20 | +public class RegionizedServerInitEvent extends Event { 21 | + private static final HandlerList HANDLER_LIST = new HandlerList(); 22 | + 23 | + @ApiStatus.Internal 24 | + public RegionizedServerInitEvent() { 25 | + super(); 26 | + } 27 | + 28 | + @Override 29 | + public @NotNull HandlerList getHandlers() { 30 | + return HANDLER_LIST; 31 | + } 32 | + 33 | + public static @NotNull HandlerList getHandlerList() { 34 | + return HANDLER_LIST; 35 | + } 36 | +} 37 | \ No newline at end of file 38 | diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java 39 | index bb09c468ab1261ea4eaf59012d30a2e45199caf7..85f557db3be4b9b688c4e2f35b37bc2dab2efcb5 100644 40 | --- a/src/main/java/org/bukkit/Bukkit.java 41 | +++ b/src/main/java/org/bukkit/Bukkit.java 42 | @@ -2908,6 +2908,14 @@ public final class Bukkit { 43 | return server.isOwnedByCurrentRegion(entity); 44 | } 45 | // Paper end - Folia region threading API 46 | + // ShreddedPaper start - region threading API 47 | + /** 48 | + * Returns whether the current thread is ticking the global region. 49 | + */ 50 | + public static boolean isGlobalTickThread() { 51 | + return server.isGlobalTickThread(); 52 | + } 53 | + // ShreddedPaper end - region threading API 54 | 55 | @NotNull 56 | public static Server.Spigot spigot() { 57 | diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java 58 | index 36d8ce7a21513600ab10b7e6f47c9f1d2cc51947..0c7c8b2f454f665e351dcd5d7230009aeca401a6 100644 59 | --- a/src/main/java/org/bukkit/Server.java 60 | +++ b/src/main/java/org/bukkit/Server.java 61 | @@ -2558,6 +2558,12 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi 62 | */ 63 | boolean isOwnedByCurrentRegion(@NotNull Entity entity); 64 | // Paper end - Folia region threading API 65 | + // ShreddedPaper start - region threading API 66 | + /** 67 | + * Returns whether the current thread is ticking the global region. 68 | + */ 69 | + public boolean isGlobalTickThread(); 70 | + // ShreddedPaper end - region threading API 71 | 72 | // Purpur start 73 | /** 74 | -------------------------------------------------------------------------------- /patches/api/0004-ShreddedPaper-branding.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Thu, 6 Jun 2024 18:25:34 +0900 4 | Subject: [PATCH] ShreddedPaper branding 5 | 6 | 7 | diff --git a/src/main/java/org/bukkit/command/defaults/VersionCommand.java b/src/main/java/org/bukkit/command/defaults/VersionCommand.java 8 | index c880d0010849ab733ad13bbd18fab3c864d0cf61..0ec64c622ebfaeb9bc607ba774620ccc7233021b 100644 9 | --- a/src/main/java/org/bukkit/command/defaults/VersionCommand.java 10 | +++ b/src/main/java/org/bukkit/command/defaults/VersionCommand.java 11 | @@ -259,7 +259,7 @@ public class VersionCommand extends BukkitCommand { 12 | // Purpur start 13 | int distance = getVersionFetcher().distance(); 14 | final Component message = Component.join(net.kyori.adventure.text.JoinConfiguration.separator(Component.newline()), 15 | - ChatColor.parseMM("Current Purpur Version: %s%s*", distance == 0 ? "" : distance > 0 ? "" : "", Bukkit.getVersion()), 16 | + ChatColor.parseMM("Current ShreddedPaper Version: %s%s*", distance == 0 ? "" : distance > 0 ? "" : "", Bukkit.getVersion()), 17 | // Purpur end 18 | msg 19 | ); 20 | -------------------------------------------------------------------------------- /patches/server/0004-Add-SimpleStampedLock.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Sun, 5 May 2024 12:48:36 +0900 4 | Subject: [PATCH] Add SimpleStampedLock 5 | 6 | 7 | diff --git a/src/main/java/io/multipaper/shreddedpaper/util/SimpleStampedLock.java b/src/main/java/io/multipaper/shreddedpaper/util/SimpleStampedLock.java 8 | new file mode 100644 9 | index 0000000000000000000000000000000000000000..99dca3671643f471223ed8ec4080d73c57fb3eb1 10 | --- /dev/null 11 | +++ b/src/main/java/io/multipaper/shreddedpaper/util/SimpleStampedLock.java 12 | @@ -0,0 +1,71 @@ 13 | +package io.multipaper.shreddedpaper.util; 14 | + 15 | +import java.util.concurrent.locks.StampedLock; 16 | +import java.util.function.Supplier; 17 | + 18 | +/** 19 | + * A java.util.concurrent.locks.StampedLock, but simpler to use and harder to 20 | + * accidentally get wrong. Removes all the boilerplate code for you. 21 | + */ 22 | +public class SimpleStampedLock { 23 | + 24 | + private final StampedLock lock = new StampedLock(); 25 | + 26 | + public void write(Runnable runnable) { 27 | + write(() -> { 28 | + runnable.run(); 29 | + return null; 30 | + }); 31 | + } 32 | + 33 | + public T write(Supplier supplier) { 34 | + lock.writeLock(); 35 | + try { 36 | + return supplier.get(); 37 | + } finally { 38 | + lock.tryUnlockWrite(); 39 | + } 40 | + } 41 | + 42 | + public void read(Runnable runnable) { 43 | + read(() -> { 44 | + runnable.run(); 45 | + return null; 46 | + }); 47 | + } 48 | + 49 | + public T read(Supplier supplier) { 50 | + lock.readLock(); 51 | + try { 52 | + return supplier.get(); 53 | + } finally { 54 | + lock.tryUnlockRead(); 55 | + } 56 | + } 57 | + 58 | + /** 59 | + * Read the value without acquiring a lock. If the read fails, it will fall 60 | + * back to acquiring the lock. 61 | + * @param supplier The method to get the value. This method may be run twice. 62 | + */ 63 | + public T optimisticRead(Supplier supplier) { 64 | + final long attempt = lock.tryOptimisticRead(); 65 | + if (attempt != 0L) { 66 | + try { 67 | + final T ret = supplier.get(); 68 | + 69 | + if (lock.validate(attempt)) { 70 | + return ret; 71 | + } 72 | + } catch (final Error error) { 73 | + throw error; 74 | + } catch (final Throwable thr) { 75 | + // ignore 76 | + } 77 | + } 78 | + 79 | + // Optimistic read failed, fall back to a read lock 80 | + return read(supplier); 81 | + } 82 | + 83 | +} 84 | -------------------------------------------------------------------------------- /patches/server/0007-Thread-safe-random.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Sun, 5 May 2024 00:43:04 +0900 4 | Subject: [PATCH] Thread-safe random 5 | 6 | 7 | diff --git a/src/main/java/net/minecraft/world/level/levelgen/LegacyRandomSource.java b/src/main/java/net/minecraft/world/level/levelgen/LegacyRandomSource.java 8 | index 0a289920a32fb8bc15024062d7b73e00759e0e49..eac33c6afe84df22452cd300c3450cdb65eabfa7 100644 9 | --- a/src/main/java/net/minecraft/world/level/levelgen/LegacyRandomSource.java 10 | +++ b/src/main/java/net/minecraft/world/level/levelgen/LegacyRandomSource.java 11 | @@ -30,6 +30,10 @@ public class LegacyRandomSource implements BitRandomSource { 12 | 13 | @Override 14 | public void setSeed(long seed) { 15 | + // ShreddedPaper start - thread safe - we don't really care about inconsistent randomness to vanilla as long as it's random 16 | + this.seed.set((seed ^ 25214903917L) & 281474976710655L); 17 | + if (true) return; 18 | + // ShreddedPaper end 19 | if (!this.seed.compareAndSet(this.seed.get(), (seed ^ 25214903917L) & 281474976710655L)) { 20 | throw ThreadingDetector.makeThreadingException("LegacyRandomSource", null); 21 | } else { 22 | @@ -39,6 +43,14 @@ public class LegacyRandomSource implements BitRandomSource { 23 | 24 | @Override 25 | public int next(int bits) { 26 | + // ShreddedPaper start - thread safe - we don't really care about inconsistent randomness to vanilla as long as it's random 27 | + long oldseed, nextseed; 28 | + do { 29 | + oldseed = this.seed.get(); 30 | + nextseed = oldseed * 25214903917L + 11L & 281474976710655L; 31 | + } while (!this.seed.compareAndSet(oldseed, nextseed)); 32 | + if (true) return (int)(nextseed >> 48 - bits); 33 | + // ShreddedPaper end 34 | long l = this.seed.get(); 35 | long m = l * 25214903917L + 11L & 281474976710655L; 36 | if (!this.seed.compareAndSet(l, m)) { 37 | -------------------------------------------------------------------------------- /patches/server/0008-Thread-safe-NeigbhorUpdater.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Sun, 5 May 2024 01:31:47 +0900 4 | Subject: [PATCH] Thread-safe NeigbhorUpdater 5 | 6 | 7 | diff --git a/src/main/java/io/multipaper/shreddedpaper/threading/PerThreadNeighborUpdater.java b/src/main/java/io/multipaper/shreddedpaper/threading/PerThreadNeighborUpdater.java 8 | new file mode 100644 9 | index 0000000000000000000000000000000000000000..e01ca0d4a18a5cea678d0fd4809291f6b7bdc731 10 | --- /dev/null 11 | +++ b/src/main/java/io/multipaper/shreddedpaper/threading/PerThreadNeighborUpdater.java 12 | @@ -0,0 +1,44 @@ 13 | +package io.multipaper.shreddedpaper.threading; 14 | + 15 | +import net.minecraft.core.BlockPos; 16 | +import net.minecraft.core.Direction; 17 | +import net.minecraft.world.level.block.Block; 18 | +import net.minecraft.world.level.block.state.BlockState; 19 | +import net.minecraft.world.level.redstone.NeighborUpdater; 20 | +import org.jetbrains.annotations.NotNull; 21 | +import org.jetbrains.annotations.Nullable; 22 | + 23 | +import java.util.function.Supplier; 24 | + 25 | +public class PerThreadNeighborUpdater implements NeighborUpdater { 26 | + 27 | + private final ThreadLocal threadLocalNeighborUpdater; 28 | + 29 | + public PerThreadNeighborUpdater(Supplier factory) { 30 | + this.threadLocalNeighborUpdater = ThreadLocal.withInitial(factory); 31 | + } 32 | + 33 | + private NeighborUpdater getOrCreate() { 34 | + return threadLocalNeighborUpdater.get(); 35 | + } 36 | + 37 | + @Override 38 | + public void shapeUpdate(@NotNull Direction direction, @NotNull BlockState neighborState, @NotNull BlockPos pos, @NotNull BlockPos neighborPos, int flags, int maxUpdateDepth) { 39 | + getOrCreate().shapeUpdate(direction, neighborState, pos, neighborPos, flags, maxUpdateDepth); 40 | + } 41 | + 42 | + @Override 43 | + public void neighborChanged(@NotNull BlockPos pos, @NotNull Block sourceBlock, @NotNull BlockPos sourcePos) { 44 | + getOrCreate().neighborChanged(pos, sourceBlock, sourcePos); 45 | + } 46 | + 47 | + @Override 48 | + public void neighborChanged(@NotNull BlockState state, @NotNull BlockPos pos, @NotNull Block sourceBlock, @NotNull BlockPos sourcePos, boolean notify) { 49 | + getOrCreate().neighborChanged(state, pos, sourceBlock, sourcePos, notify); 50 | + } 51 | + 52 | + @Override 53 | + public void updateNeighborsAtExceptFromFacing(@NotNull BlockPos pos, @NotNull Block sourceBlock, @Nullable Direction except) { 54 | + getOrCreate().updateNeighborsAtExceptFromFacing(pos, sourceBlock, except); 55 | + } 56 | +} 57 | diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java 58 | index 5e04a2cc65eca2d0cc20b1580b3d7518010f1e78..adf6379f1ebd7ead6368d7b58f60bf793ab43be6 100644 59 | --- a/src/main/java/net/minecraft/world/level/Level.java 60 | +++ b/src/main/java/net/minecraft/world/level/Level.java 61 | @@ -99,6 +99,7 @@ import org.bukkit.craftbukkit.block.data.CraftBlockData; 62 | import org.bukkit.craftbukkit.util.CraftSpawnCategory; 63 | import org.bukkit.entity.SpawnCategory; 64 | import org.bukkit.event.block.BlockPhysicsEvent; 65 | +import io.multipaper.shreddedpaper.threading.PerThreadNeighborUpdater; 66 | // CraftBukkit end 67 | 68 | public abstract class Level implements LevelAccessor, AutoCloseable { 69 | @@ -291,7 +292,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { 70 | this.thread = Thread.currentThread(); 71 | this.biomeManager = new BiomeManager(this, i); 72 | this.isDebug = flag1; 73 | - this.neighborUpdater = new CollectingNeighborUpdater(this, j); 74 | + this.neighborUpdater = new PerThreadNeighborUpdater(() -> new CollectingNeighborUpdater(this, j)); // ShreddedPaper - thread-safe 75 | this.registryAccess = iregistrycustom; 76 | this.damageSources = new DamageSources(iregistrycustom); 77 | // CraftBukkit start 78 | -------------------------------------------------------------------------------- /patches/server/0012-Level-ticks.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Fri, 17 May 2024 00:26:43 +0900 4 | Subject: [PATCH] Level ticks 5 | 6 | 7 | diff --git a/src/main/java/io/multipaper/shreddedpaper/region/LevelTicksRegionProxy.java b/src/main/java/io/multipaper/shreddedpaper/region/LevelTicksRegionProxy.java 8 | new file mode 100644 9 | index 0000000000000000000000000000000000000000..051e74363aea1aff0a9d3da4300fca9002369fc3 10 | --- /dev/null 11 | +++ b/src/main/java/io/multipaper/shreddedpaper/region/LevelTicksRegionProxy.java 12 | @@ -0,0 +1,114 @@ 13 | +package io.multipaper.shreddedpaper.region; 14 | + 15 | +import com.mojang.logging.LogUtils; 16 | +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; 17 | +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; 18 | +import net.minecraft.core.BlockPos; 19 | +import net.minecraft.core.Vec3i; 20 | +import net.minecraft.util.profiling.ProfilerFiller; 21 | +import net.minecraft.world.level.ChunkPos; 22 | +import net.minecraft.world.level.levelgen.structure.BoundingBox; 23 | +import net.minecraft.world.ticks.LevelChunkTicks; 24 | +import net.minecraft.world.ticks.LevelTicks; 25 | +import net.minecraft.world.ticks.ScheduledTick; 26 | +import org.slf4j.Logger; 27 | +import io.multipaper.shreddedpaper.util.SimpleStampedLock; 28 | + 29 | +import java.util.Optional; 30 | +import java.util.function.BiConsumer; 31 | +import java.util.function.LongPredicate; 32 | +import java.util.function.Supplier; 33 | + 34 | +public class LevelTicksRegionProxy extends LevelTicks { 35 | + 36 | + private static final Logger LOGGER = LogUtils.getClassLogger(); 37 | + 38 | + private final LongPredicate tickingFutureReadyPredicate; 39 | + private final Supplier profilerGetter; 40 | + private final Long2ObjectMap> regions = new Long2ObjectOpenHashMap<>(); 41 | + private final SimpleStampedLock regionsLock = new SimpleStampedLock(); 42 | + 43 | + public LevelTicksRegionProxy(LongPredicate tickingFutureReadyPredicate, Supplier profilerGetter) { 44 | + super(tickingFutureReadyPredicate, profilerGetter); 45 | + this.tickingFutureReadyPredicate = tickingFutureReadyPredicate; 46 | + this.profilerGetter = profilerGetter; 47 | + } 48 | + 49 | + private LevelTicks createRegionLevelTicks() { 50 | + return new LevelTicks<>(tickingFutureReadyPredicate, profilerGetter); 51 | + } 52 | + 53 | + public Optional> get(BlockPos pos) { 54 | + return get(new ChunkPos(pos)); 55 | + } 56 | + 57 | + public Optional> get(ChunkPos pos) { 58 | + return get(RegionPos.forChunk(pos)); 59 | + } 60 | + 61 | + public Optional> get(RegionPos pos) { 62 | + return Optional.ofNullable(regionsLock.optimisticRead(() -> regions.get(pos.longKey))); 63 | + } 64 | + 65 | + public void addContainer(ChunkPos pos, LevelChunkTicks scheduler) { 66 | + get(pos).orElseGet(() -> { 67 | + return regionsLock.write(() -> regions.computeIfAbsent(RegionPos.forChunk(pos).longKey, k -> createRegionLevelTicks())); 68 | + }).addContainer(pos, scheduler); 69 | + } 70 | + 71 | + public void removeContainer(ChunkPos pos) { 72 | + get(pos).ifPresent(v -> { 73 | + v.removeContainer(pos); 74 | + 75 | + if (v.isEmpty()) { 76 | + regionsLock.write(() -> regions.remove(RegionPos.forChunk(pos).longKey)); 77 | + } 78 | + }); 79 | + } 80 | + 81 | + @Override 82 | + public void schedule(ScheduledTick orderedTick) { 83 | + get(orderedTick.pos()).orElseThrow(() -> new IllegalArgumentException("Chunk not loaded: " + orderedTick.pos())).schedule(orderedTick); 84 | + } 85 | + 86 | + public void tick(RegionPos regionPos, long time, int maxTicks, BiConsumer ticker) { 87 | + get(regionPos).ifPresent(v -> { 88 | + v.tick(time, maxTicks, ticker); 89 | + }); 90 | + } 91 | + 92 | + @Override 93 | + public void tick(long time, int maxTicks, BiConsumer ticker) { 94 | + // Do nothing 95 | + } 96 | + 97 | + @Override 98 | + public boolean hasScheduledTick(BlockPos pos, T type) { 99 | + return get(pos).map(v -> v.hasScheduledTick(pos, type)).orElse(false); 100 | + } 101 | + 102 | + @Override 103 | + public boolean willTickThisTick(BlockPos pos, T type) { 104 | + return get(pos).map(v -> v.willTickThisTick(pos, type)).orElse(false); 105 | + } 106 | + 107 | + @Override 108 | + public void clearArea(BoundingBox box) { 109 | + // Surely no one will miss this 110 | + } 111 | + 112 | + @Override 113 | + public void copyArea(BoundingBox box, Vec3i offset) { 114 | + // Surely no one will miss this 115 | + } 116 | + 117 | + @Override 118 | + public void copyAreaFrom(LevelTicks scheduler, BoundingBox box, Vec3i offset) { 119 | + // Surely no one will miss this 120 | + } 121 | + 122 | + @Override 123 | + public int count() { 124 | + return -1; 125 | + } 126 | +} 127 | diff --git a/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChunkTicker.java b/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChunkTicker.java 128 | index 50044760d4511ff2b75d8866faec22bf33bd2f94..824b4535b6afffbef92df8be166e51411415fcf0 100644 129 | --- a/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChunkTicker.java 130 | +++ b/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChunkTicker.java 131 | @@ -72,6 +72,10 @@ public class ShreddedPaperChunkTicker { 132 | }); 133 | 134 | region.tickTasks(); 135 | + 136 | + level.blockTicks.tick(region.getRegionPos(), level.getGameTime(), level.paperConfig().environment.maxBlockTicks, level::tickBlock); 137 | + level.fluidTicks.tick(region.getRegionPos(), level.getGameTime(), level.paperConfig().environment.maxBlockTicks, level::tickFluid); 138 | + 139 | region.forEach(chunk -> _tickChunk(level, chunk, spawnercreature_d)); 140 | 141 | region.forEachTickingEntity(ShreddedPaperEntityTicker::tickEntity); 142 | diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java 143 | index 553d88739ae174963ee840f8a3aafa80654d45ab..2291091a8cdb3b3bb6a9af3bab6470e1906eac24 100644 144 | --- a/src/main/java/net/minecraft/server/level/ServerLevel.java 145 | +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java 146 | @@ -175,6 +175,7 @@ import org.bukkit.event.entity.CreatureSpawnEvent; 147 | import org.bukkit.event.server.MapInitializeEvent; 148 | import org.bukkit.event.weather.LightningStrikeEvent; 149 | import org.bukkit.event.world.TimeSkipEvent; 150 | +import io.multipaper.shreddedpaper.region.LevelTicksRegionProxy; 151 | import io.multipaper.shreddedpaper.region.RegionPos; 152 | import io.multipaper.shreddedpaper.threading.ShreddedPaperRegionScheduler; 153 | // CraftBukkit end 154 | @@ -201,8 +202,8 @@ public class ServerLevel extends Level implements WorldGenLevel { 155 | private final SleepStatus sleepStatus; 156 | private int emptyTime; 157 | private final PortalForcer portalForcer; 158 | - private final LevelTicks blockTicks; 159 | - private final LevelTicks fluidTicks; 160 | + public final LevelTicksRegionProxy blockTicks; // ShreddedPaper 161 | + public final LevelTicksRegionProxy fluidTicks; // ShreddedPaper 162 | private final PathTypeCache pathTypesByPosCache; 163 | final Set navigatingMobs; 164 | volatile boolean isUpdatingNavigations; 165 | @@ -705,8 +706,8 @@ public class ServerLevel extends Level implements WorldGenLevel { 166 | // CraftBukkit end 167 | this.players = new CopyOnWriteArrayList<>(); // ShreddedPaper - thread-safe (players should not be changing worlds often, thus copy-on-write is sufficient) 168 | // this.entityTickList = new EntityTickList(); // ShreddedPaper - moved into each region 169 | - this.blockTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded, this.getProfilerSupplier()); 170 | - this.fluidTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded, this.getProfilerSupplier()); 171 | + this.blockTicks = new LevelTicksRegionProxy<>(this::isPositionTickingWithEntitiesLoaded, this.getProfilerSupplier()); // ShreddedPaper 172 | + this.fluidTicks = new LevelTicksRegionProxy<>(this::isPositionTickingWithEntitiesLoaded, this.getProfilerSupplier()); // ShreddedPaper 173 | this.pathTypesByPosCache = new PathTypeCache(); 174 | this.navigatingMobs = new ObjectOpenHashSet(); 175 | this.blockEvents = new ObjectLinkedOpenHashSet(); 176 | @@ -1411,23 +1412,25 @@ public class ServerLevel extends Level implements WorldGenLevel { 177 | this.emptyTime = 0; 178 | } 179 | 180 | - private void tickFluid(BlockPos pos, Fluid fluid) { 181 | + public void tickFluid(BlockPos pos, Fluid fluid) { // ShreddedPaper - make public 182 | + TickThread.ensureTickThread(this, pos, "Cannot tick fluid outside of tick thread"); // ShreddedPaper 183 | FluidState fluid1 = this.getFluidState(pos); 184 | 185 | if (fluid1.is(fluid)) { 186 | fluid1.tick(this, pos); 187 | } 188 | - MinecraftServer.getServer().executeMidTickTasks(); // Paper - exec chunk tasks during world tick 189 | + // MinecraftServer.getServer().executeMidTickTasks(); // Paper - exec chunk tasks during world tick // ShreddedPaper - unnecessary now 190 | 191 | } 192 | 193 | - private void tickBlock(BlockPos pos, Block block) { 194 | + public void tickBlock(BlockPos pos, Block block) { // ShreddedPaper - make public 195 | + TickThread.ensureTickThread(this, pos, "Cannot tick block outside of tick thread"); // ShreddedPaper 196 | BlockState iblockdata = this.getBlockState(pos); 197 | 198 | if (iblockdata.is(block)) { 199 | iblockdata.tick(this, pos, this.random); 200 | } 201 | - MinecraftServer.getServer().executeMidTickTasks(); // Paper - exec chunk tasks during world tick 202 | + // MinecraftServer.getServer().executeMidTickTasks(); // Paper - exec chunk tasks during world tick // ShreddedPaper - unnecessary now 203 | 204 | } 205 | 206 | diff --git a/src/main/java/net/minecraft/world/level/LevelAccessor.java b/src/main/java/net/minecraft/world/level/LevelAccessor.java 207 | index 54d13eebc9b01e9d77f51011b7de95b80bc21669..4712c018da6b4b8c6c512f71133b76a18c2e966b 100644 208 | --- a/src/main/java/net/minecraft/world/level/LevelAccessor.java 209 | +++ b/src/main/java/net/minecraft/world/level/LevelAccessor.java 210 | @@ -1,6 +1,8 @@ 211 | package net.minecraft.world.level; 212 | 213 | import javax.annotation.Nullable; 214 | + 215 | +import io.papermc.paper.util.TickThread; 216 | import net.minecraft.core.BlockPos; 217 | import net.minecraft.core.Direction; 218 | import net.minecraft.core.Holder; 219 | @@ -8,6 +10,7 @@ import net.minecraft.core.particles.ParticleOptions; 220 | import net.minecraft.core.registries.Registries; 221 | import net.minecraft.resources.ResourceKey; 222 | import net.minecraft.server.MinecraftServer; 223 | +import net.minecraft.server.level.ServerLevel; 224 | import net.minecraft.sounds.SoundEvent; 225 | import net.minecraft.sounds.SoundSource; 226 | import net.minecraft.util.RandomSource; 227 | @@ -47,20 +50,24 @@ public interface LevelAccessor extends CommonLevelAccessor, LevelTimeAccess { 228 | } 229 | 230 | default void scheduleTick(BlockPos pos, Block block, int delay, TickPriority priority) { 231 | + if (this instanceof ServerLevel serverLevel) TickThread.ensureTickThread(serverLevel, pos, "Cannot schedule outside of tick thread"); // ShreddedPaper 232 | this.getBlockTicks().schedule(this.createTick(pos, block, delay, priority)); 233 | } 234 | 235 | default void scheduleTick(BlockPos pos, Block block, int delay) { 236 | + if (this instanceof ServerLevel serverLevel) TickThread.ensureTickThread(serverLevel, pos, "Cannot schedule outside of tick thread"); // ShreddedPaper 237 | this.getBlockTicks().schedule(this.createTick(pos, block, delay)); 238 | } 239 | 240 | LevelTickAccess getFluidTicks(); 241 | 242 | default void scheduleTick(BlockPos pos, Fluid fluid, int delay, TickPriority priority) { 243 | + if (this instanceof ServerLevel serverLevel) TickThread.ensureTickThread(serverLevel, pos, "Cannot schedule outside of tick thread"); // ShreddedPaper 244 | this.getFluidTicks().schedule(this.createTick(pos, fluid, delay, priority)); 245 | } 246 | 247 | default void scheduleTick(BlockPos pos, Fluid fluid, int delay) { 248 | + if (this instanceof ServerLevel serverLevel) TickThread.ensureTickThread(serverLevel, pos, "Cannot schedule outside of tick thread"); // ShreddedPaper 249 | this.getFluidTicks().schedule(this.createTick(pos, fluid, delay)); 250 | } 251 | 252 | diff --git a/src/main/java/net/minecraft/world/ticks/LevelTicks.java b/src/main/java/net/minecraft/world/ticks/LevelTicks.java 253 | index a6d62abd3102770652f914b9d697c6d3c2533cfc..5d7aeaa8dc31906713e65808f675830df23cd0e4 100644 254 | --- a/src/main/java/net/minecraft/world/ticks/LevelTicks.java 255 | +++ b/src/main/java/net/minecraft/world/ticks/LevelTicks.java 256 | @@ -282,4 +282,10 @@ public class LevelTicks implements LevelTickAccess { 257 | interface PosAndContainerConsumer { 258 | void accept(long chunkPos, LevelChunkTicks chunkTickScheduler); 259 | } 260 | + 261 | + // ShreddedPaper start 262 | + public boolean isEmpty() { 263 | + return this.allContainers.isEmpty(); 264 | + } 265 | + // ShreddedPaper end 266 | } 267 | -------------------------------------------------------------------------------- /patches/server/0013-Custom-spawners.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Sat, 18 May 2024 09:52:50 +0900 4 | Subject: [PATCH] Custom spawners 5 | 6 | 7 | diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java 8 | index 2291091a8cdb3b3bb6a9af3bab6470e1906eac24..98201d9d984fdee9cc5fff3990229b18f19f708a 100644 9 | --- a/src/main/java/net/minecraft/server/level/ServerLevel.java 10 | +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java 11 | @@ -1007,7 +1007,7 @@ public class ServerLevel extends Level implements WorldGenLevel { 12 | while (iterator.hasNext()) { 13 | CustomSpawner mobspawner = (CustomSpawner) iterator.next(); 14 | 15 | - mobspawner.tick(this, spawnMonsters, spawnAnimals); 16 | + mobspawner.tick(this, spawnMonsters, spawnAnimals); // ShreddedPaper - ensure return value remains unused 17 | } 18 | 19 | } 20 | diff --git a/src/main/java/net/minecraft/world/entity/ai/village/VillageSiege.java b/src/main/java/net/minecraft/world/entity/ai/village/VillageSiege.java 21 | index 36dec6cd78a0990ba3c09a4a748c259ef5c0a2ff..cc5be862fe53ab8c301c318d4e13f432575321f3 100644 22 | --- a/src/main/java/net/minecraft/world/entity/ai/village/VillageSiege.java 23 | +++ b/src/main/java/net/minecraft/world/entity/ai/village/VillageSiege.java 24 | @@ -17,6 +17,7 @@ import net.minecraft.world.level.CustomSpawner; 25 | import net.minecraft.world.level.levelgen.Heightmap; 26 | import net.minecraft.world.phys.Vec3; 27 | import org.slf4j.Logger; 28 | +import io.multipaper.shreddedpaper.ShreddedPaper; 29 | 30 | public class VillageSiege implements CustomSpawner { 31 | 32 | @@ -110,11 +111,12 @@ public class VillageSiege implements CustomSpawner { 33 | Vec3 vec3d = this.findRandomSpawnPos(world, new BlockPos(this.spawnX, this.spawnY, this.spawnZ)); 34 | 35 | if (vec3d != null) { 36 | + ShreddedPaper.runSync(world, new BlockPos((int) vec3d.x, (int) vec3d.y, (int) vec3d.z), () -> { // ShreddedPaper - run on block's thread 37 | Zombie entityzombie; 38 | 39 | try { 40 | entityzombie = new Zombie(world); 41 | - entityzombie.finalizeSpawn(world, world.getCurrentDifficultyAt(entityzombie.blockPosition()), MobSpawnType.EVENT, (SpawnGroupData) null); 42 | + entityzombie.finalizeSpawn(world, world.getCurrentDifficultyAt(new BlockPos((int) vec3d.x, (int) vec3d.y, (int) vec3d.z)), MobSpawnType.EVENT, (SpawnGroupData) null); // ShreddedPaper - use zombie's planned position, not 0,0 43 | } catch (Exception exception) { 44 | VillageSiege.LOGGER.warn("Failed to create zombie for village siege at {}", vec3d, exception); 45 | com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); // Paper - ServerExceptionEvent 46 | @@ -123,6 +125,7 @@ public class VillageSiege implements CustomSpawner { 47 | 48 | entityzombie.moveTo(vec3d.x, vec3d.y, vec3d.z, world.random.nextFloat() * 360.0F, 0.0F); 49 | world.addFreshEntityWithPassengers(entityzombie, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_INVASION); // CraftBukkit 50 | + }); // ShreddedPaper - run on block's thread 51 | } 52 | } 53 | 54 | diff --git a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java 55 | index d503d7a5837dbeb98e58dbe8f7e5de45f6d88990..eb006f592e63576bc54ca0d7773c26294636074a 100644 56 | --- a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java 57 | +++ b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java 58 | @@ -32,6 +32,7 @@ public class CatSpawner implements CustomSpawner { 59 | if (player == null) { 60 | return 0; 61 | } else { 62 | + player.getBukkitEntity().taskScheduler.schedule(e -> CustomSpawner.voidReturnValue(() -> { // ShreddedPaper - run on entity's thread 63 | RandomSource randomSource = world.random; 64 | int i = (8 + randomSource.nextInt(24)) * (randomSource.nextBoolean() ? -1 : 1); 65 | int j = (8 + randomSource.nextInt(24)) * (randomSource.nextBoolean() ? -1 : 1); 66 | @@ -52,6 +53,8 @@ public class CatSpawner implements CustomSpawner { 67 | 68 | return 0; 69 | } 70 | + }), e -> {}, 1); // ShreddedPaper - run on entity's thread 71 | + return 1; // ShreddedPaper - return value is unused, let's just say 1 72 | } 73 | } 74 | } else { 75 | diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java 76 | index 96e9fce5f9084737d2fcf4deb83305733b480179..1db6ff8e4d03395da0c2a013ff597bea2a5e55dd 100644 77 | --- a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java 78 | +++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java 79 | @@ -104,6 +104,7 @@ public class WanderingTraderSpawner implements CustomSpawner { 80 | } else if (this.random.nextInt(10) != 0) { 81 | return false; 82 | } else { 83 | + entityplayer.getBukkitEntity().taskScheduler.schedule(e -> CustomSpawner.voidReturnValue(() -> { // ShreddedPaper - run on entity's thread 84 | BlockPos blockposition = entityplayer.blockPosition(); 85 | boolean flag = true; 86 | PoiManager villageplace = world.getPoiManager(); 87 | @@ -136,6 +137,8 @@ public class WanderingTraderSpawner implements CustomSpawner { 88 | } 89 | 90 | return false; 91 | + }), e -> {}, 1); // ShreddedPaper - run on entity's thread 92 | + return true; // ShreddedPaper - assume success? 93 | } 94 | } 95 | 96 | diff --git a/src/main/java/net/minecraft/world/level/CustomSpawner.java b/src/main/java/net/minecraft/world/level/CustomSpawner.java 97 | index 537efd59aedc42b88d24662d5018f7103fd6dc25..d5104713889911e7543d013774ba371f23d2a454 100644 98 | --- a/src/main/java/net/minecraft/world/level/CustomSpawner.java 99 | +++ b/src/main/java/net/minecraft/world/level/CustomSpawner.java 100 | @@ -2,6 +2,14 @@ package net.minecraft.world.level; 101 | 102 | import net.minecraft.server.level.ServerLevel; 103 | 104 | +import java.util.function.Supplier; 105 | + 106 | public interface CustomSpawner { 107 | int tick(ServerLevel world, boolean spawnMonsters, boolean spawnAnimals); 108 | + 109 | + // ShreddedPaper start 110 | + public static void voidReturnValue(Supplier supplier) { 111 | + supplier.get(); 112 | + } 113 | + // ShreddedPaper end 114 | } 115 | diff --git a/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java 116 | index 1741360aa3f2409b1a8ddf1d4602ffe57651a586..c9618646e9412dcb8d1d40ae451a9599c875bd37 100644 117 | --- a/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java 118 | +++ b/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java 119 | @@ -56,6 +56,7 @@ public class PatrolSpawner implements CustomSpawner { 120 | if (patrolSpawnDelay > 0) { 121 | return 0; 122 | } else { 123 | + entityhuman.getBukkitEntity().taskScheduler.schedule(e -> CustomSpawner.voidReturnValue(() -> { // ShreddedPaper - run on entity's thread 124 | long days; 125 | if (world.paperConfig().entities.behavior.pillagerPatrols.start.perPlayer) { 126 | days = entityhuman.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.PLAY_TIME)) / 24000L; // PLAY_ONE_MINUTE is actually counting in ticks, a misnomer by Mojang 127 | @@ -123,6 +124,8 @@ public class PatrolSpawner implements CustomSpawner { 128 | } else { 129 | return 0; 130 | } 131 | + }), e -> {}, 1); // ShreddedPaper - run on entity's thread 132 | + return 1; // ShreddedPaper - return value is unused, let's just say 1 133 | } 134 | } 135 | } 136 | diff --git a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java 137 | index 04f67f7b43d2f461c776c76614dc3e5f060aea63..21104075c85efa41b2f56d6426ec20547b393ca8 100644 138 | --- a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java 139 | +++ b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java 140 | @@ -57,6 +57,7 @@ public class PhantomSpawner implements CustomSpawner { 141 | while (iterator.hasNext()) { 142 | ServerPlayer entityplayer = (ServerPlayer) iterator.next(); 143 | 144 | + entityplayer.getBukkitEntity().taskScheduler.schedule(e -> { // ShreddedPaper - run on entity's thread 145 | if (!entityplayer.isSpectator() && (!world.paperConfig().entities.behavior.phantomsDoNotSpawnOnCreativePlayers || !entityplayer.isCreative())) { // Paper - Add phantom creative and insomniac controls 146 | BlockPos blockposition = entityplayer.blockPosition(); 147 | 148 | @@ -94,7 +95,7 @@ public class PhantomSpawner implements CustomSpawner { 149 | entityphantom.moveTo(blockposition1, 0.0F, 0.0F); 150 | groupdataentity = entityphantom.finalizeSpawn(world, difficultydamagescaler, MobSpawnType.NATURAL, groupdataentity); 151 | world.addFreshEntityWithPassengers(entityphantom, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit 152 | - ++i; 153 | + // ++i; // ShreddedPaper - value is unused 154 | } 155 | } 156 | } 157 | @@ -102,6 +103,7 @@ public class PhantomSpawner implements CustomSpawner { 158 | } 159 | } 160 | } 161 | + }, e -> {}, 1); // ShreddedPaper - run on entity's thread 162 | } 163 | 164 | return i; 165 | -------------------------------------------------------------------------------- /patches/server/0015-mpmap-debug-command.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Tue, 21 May 2024 23:54:30 +0900 4 | Subject: [PATCH] mpmap debug command 5 | 6 | 7 | diff --git a/src/main/java/io/multipaper/shreddedpaper/commands/MPMapCommand.java b/src/main/java/io/multipaper/shreddedpaper/commands/MPMapCommand.java 8 | new file mode 100644 9 | index 0000000000000000000000000000000000000000..6fad03268943c9e4d4a6be3afac14f43da5e83d2 10 | --- /dev/null 11 | +++ b/src/main/java/io/multipaper/shreddedpaper/commands/MPMapCommand.java 12 | @@ -0,0 +1,48 @@ 13 | +package io.multipaper.shreddedpaper.commands; 14 | + 15 | +import io.papermc.paper.chunk.system.scheduling.NewChunkHolder; 16 | +import net.kyori.adventure.text.format.NamedTextColor; 17 | +import net.minecraft.server.level.FullChunkStatus; 18 | +import net.minecraft.server.level.ServerLevel; 19 | +import net.minecraft.world.level.ChunkPos; 20 | +import org.bukkit.craftbukkit.entity.CraftPlayer; 21 | +import org.bukkit.entity.Player; 22 | +import io.multipaper.shreddedpaper.ShreddedPaper; 23 | + 24 | +public class MPMapCommand extends MapCommandBase { 25 | + 26 | + public MPMapCommand(String command) { 27 | + super(command); 28 | + setPermission("shreddedpaper.command.mpmap"); 29 | + } 30 | + 31 | + @Override 32 | + protected ChunkStatus getStatus(Player player, ChunkPos chunkPos) { 33 | + NewChunkHolder newChunkHolder = ((ServerLevel) ((CraftPlayer) player).getHandle().level()).chunkTaskScheduler.chunkHolderManager.getChunkHolder(chunkPos.x, chunkPos.z); 34 | + 35 | + NamedTextColor color; 36 | + String name; 37 | + 38 | + if (newChunkHolder == null) { 39 | + color = NamedTextColor.DARK_GRAY; 40 | + name = "Unloaded"; 41 | + } else if (newChunkHolder.getChunkStatus() == FullChunkStatus.INACCESSIBLE) { 42 | + color = NamedTextColor.GRAY; 43 | + name = "Inaccessible"; 44 | + } else if (newChunkHolder.getChunkStatus() == FullChunkStatus.FULL) { 45 | + color = NamedTextColor.WHITE; 46 | + name = "Full"; 47 | + } else if (newChunkHolder.getChunkStatus() == FullChunkStatus.BLOCK_TICKING) { 48 | + color = NamedTextColor.BLUE; 49 | + name = "Block Ticking"; 50 | + } else if (newChunkHolder.getChunkStatus() == FullChunkStatus.ENTITY_TICKING) { 51 | + color = NamedTextColor.AQUA; 52 | + name = "Entity Ticking"; 53 | + } else { 54 | + color = NamedTextColor.RED; 55 | + name = "Unknown"; 56 | + } 57 | + 58 | + return new ChunkStatus(color, name); 59 | + } 60 | +} 61 | diff --git a/src/main/java/io/multipaper/shreddedpaper/commands/MapCommandBase.java b/src/main/java/io/multipaper/shreddedpaper/commands/MapCommandBase.java 62 | new file mode 100644 63 | index 0000000000000000000000000000000000000000..da00cc8e1cc540b5d042996837b4f546b2b361cb 64 | --- /dev/null 65 | +++ b/src/main/java/io/multipaper/shreddedpaper/commands/MapCommandBase.java 66 | @@ -0,0 +1,93 @@ 67 | +package io.multipaper.shreddedpaper.commands; 68 | + 69 | +import net.kyori.adventure.text.Component; 70 | +import net.kyori.adventure.text.event.HoverEvent; 71 | +import net.kyori.adventure.text.format.NamedTextColor; 72 | +import net.minecraft.world.level.ChunkPos; 73 | +import org.bukkit.ChatColor; 74 | +import org.bukkit.command.Command; 75 | +import org.bukkit.command.CommandSender; 76 | +import org.bukkit.craftbukkit.entity.CraftPlayer; 77 | +import org.bukkit.entity.Player; 78 | +import org.jetbrains.annotations.NotNull; 79 | + 80 | +import javax.annotation.Nullable; 81 | +import java.util.Objects; 82 | + 83 | +public abstract class MapCommandBase extends Command { 84 | + 85 | + public MapCommandBase(String command) { 86 | + super(command); 87 | + } 88 | + 89 | + protected abstract ChunkStatus getStatus(Player player, ChunkPos chunkPos); 90 | + 91 | + @Override 92 | + public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, String[] args) { 93 | + if (!testPermission(sender)) return false; 94 | + 95 | + if (!(sender instanceof Player player)) { 96 | + sender.sendMessage(ChatColor.RED + "Only players can execute this command."); 97 | + return false; 98 | + } 99 | + 100 | + sendMap(player); 101 | + 102 | + return true; 103 | + } 104 | + 105 | + protected void sendMap(Player player) { 106 | + sendMap(player, 8); 107 | + } 108 | + 109 | + protected void sendMap(Player player, int radius) { 110 | + sendHeader(player, radius); 111 | + for (int row = -radius; row <= radius; row++) { 112 | + sendRow(player, radius, row); 113 | + } 114 | + sendHeader(player, radius); 115 | + } 116 | + 117 | + private ChunkStatus getStatus(Player player, int x, int z) { 118 | + return getStatus(player, new ChunkPos(((CraftPlayer) player).getHandle().blockPosition().offset(x << 4, 0, z << 4))); 119 | + } 120 | + 121 | + private void sendRow(Player player, int radius, int row) { 122 | + Component component = Component.text(" | ").color(NamedTextColor.GOLD); 123 | + 124 | + int i = -radius; 125 | + while (i <= radius) { 126 | + StringBuilder builder = new StringBuilder(); 127 | + ChunkStatus status = getStatus(player, i, row); 128 | + 129 | + while (i <= radius) { 130 | + ChunkStatus status2 = getStatus(player, i, row); 131 | + 132 | + if (!Objects.equals(status, status2)) { 133 | + break; 134 | + } 135 | + 136 | + builder.append(row == 0 && i == 0 ? "\u25A0 " : "+ "); 137 | + 138 | + i++; 139 | + } 140 | + 141 | + Component innerComponent = Component.text(builder.toString()).color(status.color()); 142 | + 143 | + if (status.description() != null) { 144 | + innerComponent = innerComponent.hoverEvent(HoverEvent.showText(Component.text(status.description()).color(status.color()))); 145 | + } 146 | + 147 | + component = component.append(innerComponent); 148 | + } 149 | + 150 | + component = component.append(Component.text("| ").color(NamedTextColor.GOLD)); 151 | + player.sendMessage(component); 152 | + } 153 | + 154 | + private void sendHeader(Player player, int radius) { 155 | + player.sendMessage(Component.text("+ " + "- ".repeat(radius * 2 + 1) + "+").color(NamedTextColor.GOLD)); 156 | + } 157 | + 158 | + protected record ChunkStatus(NamedTextColor color, @Nullable String description) {}; 159 | +} 160 | diff --git a/src/main/java/io/multipaper/shreddedpaper/commands/ShreddedPaperCommands.java b/src/main/java/io/multipaper/shreddedpaper/commands/ShreddedPaperCommands.java 161 | new file mode 100644 162 | index 0000000000000000000000000000000000000000..4dd39bce7ed93bf96cc893cc5e3cf539f44763fe 163 | --- /dev/null 164 | +++ b/src/main/java/io/multipaper/shreddedpaper/commands/ShreddedPaperCommands.java 165 | @@ -0,0 +1,26 @@ 166 | +package io.multipaper.shreddedpaper.commands; 167 | + 168 | +import net.minecraft.server.MinecraftServer; 169 | +import org.bukkit.command.Command; 170 | + 171 | +import java.util.HashMap; 172 | +import java.util.Map; 173 | + 174 | +public class ShreddedPaperCommands { 175 | + 176 | + private static final Map COMMANDS = new HashMap<>(); 177 | + static { 178 | + for (Command command : new Command[] { 179 | + new MPMapCommand("mpmap") 180 | + }) { 181 | + COMMANDS.put(command.getName(), command); 182 | + } 183 | + } 184 | + 185 | + public static void registerCommands(final MinecraftServer server) { 186 | + COMMANDS.forEach((s, command) -> { 187 | + server.server.getCommandMap().register(s, "shreddedpaper", command); 188 | + }); 189 | + } 190 | + 191 | +} 192 | diff --git a/src/main/java/io/multipaper/shreddedpaper/permissions/ShreddedPaperCommandPermissions.java b/src/main/java/io/multipaper/shreddedpaper/permissions/ShreddedPaperCommandPermissions.java 193 | index 53dcfbdf38f007fc86064ac1a495288d5f25f598..89436f82a49d69f3bd21195bf44dfc4fa9bd4df7 100644 194 | --- a/src/main/java/io/multipaper/shreddedpaper/permissions/ShreddedPaperCommandPermissions.java 195 | +++ b/src/main/java/io/multipaper/shreddedpaper/permissions/ShreddedPaperCommandPermissions.java 196 | @@ -1,6 +1,7 @@ 197 | package io.multipaper.shreddedpaper.permissions; 198 | 199 | import org.bukkit.permissions.Permission; 200 | +import org.bukkit.permissions.PermissionDefault; 201 | import org.bukkit.util.permissions.DefaultPermissions; 202 | import org.jetbrains.annotations.NotNull; 203 | 204 | @@ -11,6 +12,7 @@ public class ShreddedPaperCommandPermissions { 205 | public static void registerPermissions(@NotNull Permission parent) { 206 | Permission commands = DefaultPermissions.registerPermission(ROOT, "Gives the user the ability to use all ShreddedPaper commands", parent); 207 | 208 | + DefaultPermissions.registerPermission(PREFIX + "mpmap", "MPMap command", PermissionDefault.TRUE, commands); 209 | 210 | commands.recalculatePermissibles(); 211 | } 212 | diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java 213 | index 60444929e3e0fb6e53f6df2b41cbc52222d26600..d281fe5f75f6f99f869fa3cc8da4f1ff9f96a7bf 100644 214 | --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java 215 | +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java 216 | @@ -69,6 +69,7 @@ import org.bukkit.craftbukkit.util.TerminalConsoleWriterThread; 217 | import org.bukkit.event.server.ServerCommandEvent; 218 | import org.bukkit.craftbukkit.util.Waitable; // Paper 219 | import org.bukkit.event.server.RemoteServerCommandEvent; 220 | +import io.multipaper.shreddedpaper.commands.ShreddedPaperCommands; 221 | // CraftBukkit end 222 | 223 | public class DedicatedServer extends MinecraftServer implements ServerInterface { 224 | @@ -242,6 +243,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface 225 | } 226 | org.purpurmc.purpur.PurpurConfig.registerCommands(); 227 | // Purpur end 228 | + ShreddedPaperCommands.registerCommands(this); // ShreddedPaper 229 | com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now 230 | gg.pufferfish.pufferfish.PufferfishConfig.load(); // Pufferfish 231 | gg.pufferfish.pufferfish.PufferfishCommand.init(); // Pufferfish 232 | -------------------------------------------------------------------------------- /patches/server/0016-EntityLookup-accessibleEntities.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Fri, 24 May 2024 00:03:50 +0900 4 | Subject: [PATCH] EntityLookup accessibleEntities 5 | 6 | Use a synchronized block as data is only read rarely for debug reports 7 | 8 | diff --git a/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java b/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java 9 | index 15ee41452992714108efe53b708b5a4e1da7c1ff..e22012b5854c9e7725780cf8f3173949202c1472 100644 10 | --- a/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java 11 | +++ b/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java 12 | @@ -191,11 +191,15 @@ public final class EntityLookup implements LevelEntityGetter { 13 | 14 | @Override 15 | public Iterable getAll() { 16 | + synchronized (this.accessibleEntities) { // ShreddedPaper 17 | return new ArrayIterable<>(this.accessibleEntities.getRawData(), 0, this.accessibleEntities.size()); 18 | + } // ShreddedPaper 19 | } 20 | 21 | public Entity[] getAllCopy() { 22 | + synchronized (this.accessibleEntities) { // ShreddedPaper 23 | return Arrays.copyOf(this.accessibleEntities.getRawData(), this.accessibleEntities.size(), Entity[].class); 24 | + } // ShreddedPaper 25 | } 26 | 27 | @Override 28 | @@ -277,7 +281,9 @@ public final class EntityLookup implements LevelEntityGetter { 29 | if (newVisibility.ordinal() > oldVisibility.ordinal()) { 30 | // status upgrade 31 | if (!oldVisibility.isAccessible() && newVisibility.isAccessible()) { 32 | + synchronized (this.accessibleEntities) { // ShreddedPaper 33 | this.accessibleEntities.add(entity); 34 | + } // ShreddedPaper 35 | EntityLookup.this.worldCallback.onTrackingStart(entity); 36 | } 37 | 38 | @@ -291,7 +297,9 @@ public final class EntityLookup implements LevelEntityGetter { 39 | } 40 | 41 | if (oldVisibility.isAccessible() && !newVisibility.isAccessible()) { 42 | + synchronized (this.accessibleEntities) { // ShreddedPaper 43 | this.accessibleEntities.remove(entity); 44 | + } // ShreddedPaper 45 | EntityLookup.this.worldCallback.onTrackingEnd(entity); 46 | } 47 | } 48 | diff --git a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java 49 | index 777b789fdcdf297309cfb36fc7f77e3fdb6327ca..b35cd0b4434c8f0cf078fc1cdf7c683517dc6b6b 100644 50 | --- a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java 51 | +++ b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java 52 | @@ -103,6 +103,8 @@ public final class EntityCommand implements PaperSubcommand { 53 | Map nonEntityTicking = Maps.newHashMap(); 54 | ServerChunkCache chunkProviderServer = world.getChunkSource(); 55 | world.getAllEntities().forEach(e -> { 56 | + if (e == null) return; // ShreddedPaper - Concurrent modification 57 | + 58 | ResourceLocation key = EntityType.getKey(e.getType()); 59 | 60 | MutablePair> info = list.computeIfAbsent(key, k -> MutablePair.of(0, Maps.newHashMap())); 61 | diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java 62 | index 0f90a6803851eba51e164772c984b1cd1193d882..d483c94b8afd624af4c0c165a809d3a5853ad02e 100644 63 | --- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java 64 | +++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java 65 | @@ -80,6 +80,8 @@ public final class NaturalSpawner { 66 | while (iterator.hasNext()) { 67 | Entity entity = (Entity) iterator.next(); 68 | 69 | + if (entity == null) continue; // ShreddedPaper - concurrent modification 70 | + 71 | if (entity instanceof Mob entityinsentient) { 72 | if (entityinsentient.isPersistenceRequired() || entityinsentient.requiresCustomPersistence()) { 73 | continue; 74 | -------------------------------------------------------------------------------- /patches/server/0017-World-gen.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Fri, 24 May 2024 16:44:47 +0900 4 | Subject: [PATCH] World gen 5 | 6 | 7 | diff --git a/src/main/java/net/minecraft/server/level/PlayerRespawnLogic.java b/src/main/java/net/minecraft/server/level/PlayerRespawnLogic.java 8 | index 5bfdd772f2e8933acca69ed62c04755a5655fa94..e1ee330b6fdd43114a1330677b1597bf437c1ed5 100644 9 | --- a/src/main/java/net/minecraft/server/level/PlayerRespawnLogic.java 10 | +++ b/src/main/java/net/minecraft/server/level/PlayerRespawnLogic.java 11 | @@ -10,6 +10,7 @@ import net.minecraft.world.level.block.Block; 12 | import net.minecraft.world.level.block.state.BlockState; 13 | import net.minecraft.world.level.chunk.LevelChunk; 14 | import net.minecraft.world.level.levelgen.Heightmap; 15 | +import io.multipaper.shreddedpaper.region.RegionPos; 16 | 17 | public class PlayerRespawnLogic { 18 | @Nullable 19 | @@ -48,6 +49,7 @@ public class PlayerRespawnLogic { 20 | if (SharedConstants.debugVoidTerrain(chunkPos)) { 21 | return null; 22 | } else { 23 | + return world.chunkScheduler.getRegionLocker().lockRegion(RegionPos.forChunk(chunkPos), () -> { // ShreddedPaper - lock region 24 | for (int i = chunkPos.getMinBlockX(); i <= chunkPos.getMaxBlockX(); i++) { 25 | for (int j = chunkPos.getMinBlockZ(); j <= chunkPos.getMaxBlockZ(); j++) { 26 | BlockPos blockPos = getOverworldRespawnPos(world, i, j); 27 | @@ -58,6 +60,7 @@ public class PlayerRespawnLogic { 28 | } 29 | 30 | return null; 31 | + }); // ShreddedPaper 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /patches/server/0019-Multithreaded-WeakSeqLock.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Sat, 25 May 2024 02:33:24 +0900 4 | Subject: [PATCH] Multithreaded WeakSeqLock 5 | 6 | 7 | diff --git a/src/main/java/com/destroystokyo/paper/util/concurrent/WeakSeqLock.java b/src/main/java/com/destroystokyo/paper/util/concurrent/WeakSeqLock.java 8 | index 4029dc68cf35d63aa70c4a76c35bf65a7fc6358f..7d0cd938c9ce83845657a21a120d4a2a74376fc4 100644 9 | --- a/src/main/java/com/destroystokyo/paper/util/concurrent/WeakSeqLock.java 10 | +++ b/src/main/java/com/destroystokyo/paper/util/concurrent/WeakSeqLock.java 11 | @@ -16,8 +16,21 @@ public final class WeakSeqLock { 12 | } 13 | 14 | public void acquireWrite() { 15 | + // ShreddedPaper start - Only one thread can hold the write lock at a time 16 | + int failures = 0; 17 | + long curr; 18 | + 19 | + for (curr = this.lock.get(); !this.canRead(curr) || !lock.compareAndSet(curr, curr + 1); curr = this.lock.get()) { 20 | + 21 | + if (++failures > 5_000) { 22 | + Thread.yield(); 23 | + } 24 | + 25 | + } 26 | + 27 | // must be release-type write 28 | - this.lock.lazySet(this.lock.get() + 1); 29 | + // this.lock.lazySet(this.lock.get() + 1); 30 | + // ShreddedPaper end - Only one thread can hold the write lock at a time 31 | } 32 | 33 | public boolean canRead(final long read) { 34 | -------------------------------------------------------------------------------- /patches/server/0027-Thread-safe-AreaMap.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Thu, 30 May 2024 17:43:14 +0900 4 | Subject: [PATCH] Thread-safe AreaMap 5 | 6 | 7 | diff --git a/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java 8 | index 091b1ae822e1c0517e59572e7a9bda11e998c0ee..4ffbf6c8d00db0d94e0e2b022202632aed2f3385 100644 9 | --- a/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java 10 | +++ b/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java 11 | @@ -1,5 +1,6 @@ 12 | package com.destroystokyo.paper.util.misc; 13 | 14 | +import io.multipaper.shreddedpaper.util.SimpleStampedLock; 15 | import io.papermc.paper.util.IntegerUtil; 16 | import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; 17 | import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; 18 | @@ -33,6 +34,8 @@ public abstract class AreaMap { 19 | protected final ChangeCallback removeCallback; 20 | protected final ChangeSourceCallback changeSourceCallback; 21 | 22 | + protected final SimpleStampedLock lock = new SimpleStampedLock(); // ShreddedPaper - Multi-threaded access 23 | + 24 | public AreaMap() { 25 | this(new PooledLinkedHashSets<>()); 26 | } 27 | @@ -54,35 +57,36 @@ public abstract class AreaMap { 28 | 29 | @Nullable 30 | public final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getObjectsInRange(final long key) { 31 | - return this.areaMap.get(key); 32 | + return this.lock.optimisticRead(() -> this.areaMap.get(key)); // ShreddedPaper - Multi-threaded access 33 | } 34 | 35 | @Nullable 36 | public final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getObjectsInRange(final ChunkPos chunkPos) { 37 | - return this.areaMap.get(MCUtil.getCoordinateKey(chunkPos)); 38 | + return this.lock.optimisticRead(() -> this.areaMap.get(MCUtil.getCoordinateKey(chunkPos))); // ShreddedPaper - Multi-threaded access 39 | } 40 | 41 | @Nullable 42 | public final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getObjectsInRange(final int chunkX, final int chunkZ) { 43 | - return this.areaMap.get(MCUtil.getCoordinateKey(chunkX, chunkZ)); 44 | + return this.lock.optimisticRead(() -> this.areaMap.get(MCUtil.getCoordinateKey(chunkX, chunkZ))); // ShreddedPaper - Multi-threaded access 45 | } 46 | 47 | // Long.MIN_VALUE indicates the object is not mapped 48 | public final long getLastCoordinate(final E object) { 49 | - return this.objectToLastCoordinate.getOrDefault(object, Long.MIN_VALUE); 50 | + return this.lock.optimisticRead(() -> this.objectToLastCoordinate.getOrDefault(object, Long.MIN_VALUE)); // ShreddedPaper - Multi-threaded access 51 | } 52 | 53 | // -1 indicates the object is not mapped 54 | public final int getLastViewDistance(final E object) { 55 | - return this.objectToViewDistance.getOrDefault(object, -1); 56 | + return this.lock.optimisticRead(() -> this.objectToViewDistance.getOrDefault(object, -1)); // ShreddedPaper - Multi-threaded access 57 | } 58 | 59 | // returns the total number of mapped chunks 60 | public final int size() { 61 | - return this.areaMap.size(); 62 | + return this.lock.optimisticRead(this.areaMap::size); // ShreddedPaper - Multi-threaded access 63 | } 64 | 65 | public final void addOrUpdate(final E object, final int chunkX, final int chunkZ, final int viewDistance) { 66 | + this.lock.write(() -> { // ShreddedPaper - Multi-threaded access 67 | final int oldViewDistance = this.objectToViewDistance.put(object, viewDistance); 68 | final long newPos = MCUtil.getCoordinateKey(chunkX, chunkZ); 69 | final long oldPos = this.objectToLastCoordinate.put(object, newPos); 70 | @@ -95,9 +99,11 @@ public abstract class AreaMap { 71 | this.updateObjectCallback(object, oldPos, newPos, oldViewDistance, viewDistance); 72 | } 73 | //this.validate(object, viewDistance); 74 | + }); // ShreddedPaper - Multi-threaded access 75 | } 76 | 77 | public final boolean update(final E object, final int chunkX, final int chunkZ, final int viewDistance) { 78 | + return this.lock.write(() -> { // ShreddedPaper - Multi-threaded access 79 | final int oldViewDistance = this.objectToViewDistance.replace(object, viewDistance); 80 | if (oldViewDistance == -1) { 81 | return false; 82 | @@ -109,6 +115,7 @@ public abstract class AreaMap { 83 | } 84 | //this.validate(object, viewDistance); 85 | return true; 86 | + }); // ShreddedPaper - Multi-threaded access 87 | } 88 | 89 | // called after the distance map updates 90 | @@ -119,6 +126,7 @@ public abstract class AreaMap { 91 | } 92 | 93 | public final boolean add(final E object, final int chunkX, final int chunkZ, final int viewDistance) { 94 | + return this.lock.write(() -> { // ShreddedPaper - Multi-threaded access 95 | final int oldViewDistance = this.objectToViewDistance.putIfAbsent(object, viewDistance); 96 | if (oldViewDistance != -1) { 97 | return false; 98 | @@ -132,12 +140,14 @@ public abstract class AreaMap { 99 | //this.validate(object, viewDistance); 100 | 101 | return true; 102 | + }); // ShreddedPaper - Multi-threaded access 103 | } 104 | 105 | // called after the distance map updates 106 | protected void addObjectCallback(final E object, final int chunkX, final int chunkZ, final int viewDistance) {} 107 | 108 | public final boolean remove(final E object) { 109 | + return this.lock.write(() -> { // ShreddedPaper - Multi-threaded access 110 | final long position = this.objectToLastCoordinate.removeLong(object); 111 | final int viewDistance = this.objectToViewDistance.removeInt(object); 112 | 113 | @@ -152,6 +162,7 @@ public abstract class AreaMap { 114 | this.removeObjectCallback(object, currentX, currentZ, viewDistance); 115 | //this.validate(object, -1); 116 | return true; 117 | + }); // ShreddedPaper - Multi-threaded access 118 | } 119 | 120 | // called after the distance map updates 121 | diff --git a/src/main/java/com/destroystokyo/paper/util/misc/PooledLinkedHashSets.java b/src/main/java/com/destroystokyo/paper/util/misc/PooledLinkedHashSets.java 122 | index e51104e65a07b6ea7bbbcbb6afb066ef6401cc5b..ba1e9413cee8f9b12023d21567384aa9420b7fc4 100644 123 | --- a/src/main/java/com/destroystokyo/paper/util/misc/PooledLinkedHashSets.java 124 | +++ b/src/main/java/com/destroystokyo/paper/util/misc/PooledLinkedHashSets.java 125 | @@ -24,7 +24,7 @@ public class PooledLinkedHashSets { 126 | return; 127 | } 128 | 129 | - public PooledObjectLinkedOpenHashSet findMapWith(final PooledObjectLinkedOpenHashSet current, final E object) { 130 | + public synchronized PooledObjectLinkedOpenHashSet findMapWith(final PooledObjectLinkedOpenHashSet current, final E object) { // ShreddedPaper - Multi-threaded access 131 | final PooledObjectLinkedOpenHashSet cached = current.getAddCache(object); 132 | 133 | if (cached != null) { 134 | @@ -76,7 +76,7 @@ public class PooledLinkedHashSets { 135 | } 136 | 137 | // rets null if current.size() == 1 138 | - public PooledObjectLinkedOpenHashSet findMapWithout(final PooledObjectLinkedOpenHashSet current, final E object) { 139 | + public synchronized PooledObjectLinkedOpenHashSet findMapWithout(final PooledObjectLinkedOpenHashSet current, final E object) { // ShreddedPaper - Multi-threaded access 140 | if (current.set.size() == 1) { 141 | decrementReferenceCount(current); 142 | return null; 143 | -------------------------------------------------------------------------------- /patches/server/0028-Threaded-chunk-changes-broadcasting.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Thu, 30 May 2024 20:37:51 +0900 4 | Subject: [PATCH] Threaded chunk changes broadcasting 5 | 6 | 7 | diff --git a/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChangesBroadcaster.java b/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChangesBroadcaster.java 8 | new file mode 100644 9 | index 0000000000000000000000000000000000000000..ad7960c9a11cffc4b89a08dac45dd0056d12d668 10 | --- /dev/null 11 | +++ b/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChangesBroadcaster.java 12 | @@ -0,0 +1,53 @@ 13 | +package io.multipaper.shreddedpaper.threading; 14 | + 15 | +import io.papermc.paper.util.TickThread; 16 | +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; 17 | +import net.minecraft.server.level.ChunkHolder; 18 | + 19 | +public class ShreddedPaperChangesBroadcaster { 20 | + 21 | + private static final ThreadLocal> needsChangeBroadcastingThreadLocal = new ThreadLocal<>(); 22 | + 23 | + public static void setAsWorkerThread() { 24 | + if (needsChangeBroadcastingThreadLocal.get() == null) { 25 | + needsChangeBroadcastingThreadLocal.set(new ReferenceOpenHashSet<>()); 26 | + } 27 | + } 28 | + 29 | + public static void add(ChunkHolder chunkHolder) { 30 | + ReferenceOpenHashSet needsChangeBroadcasting = needsChangeBroadcastingThreadLocal.get(); 31 | + if (needsChangeBroadcasting != null) { 32 | + needsChangeBroadcasting.add(chunkHolder); 33 | + } 34 | + } 35 | + 36 | + public static void remove(ChunkHolder chunkHolder) { 37 | + ReferenceOpenHashSet needsChangeBroadcasting = needsChangeBroadcastingThreadLocal.get(); 38 | + if (needsChangeBroadcasting != null) { 39 | + needsChangeBroadcasting.remove(chunkHolder); 40 | + } 41 | + } 42 | + 43 | + public static void broadcastChanges() { 44 | + broadcastChanges(needsChangeBroadcastingThreadLocal.get()); 45 | + } 46 | + 47 | + public static void broadcastChanges(ReferenceOpenHashSet needsChangeBroadcasting) { 48 | + if (!needsChangeBroadcasting.isEmpty()) { 49 | + ReferenceOpenHashSet copy = needsChangeBroadcasting.clone(); 50 | + needsChangeBroadcasting.clear(); 51 | + for (ChunkHolder holder : copy) { 52 | + if (!TickThread.isTickThreadFor(holder.newChunkHolder.world, holder.pos)) { 53 | + // The changes will get picked up by the correct thread when it is ticked 54 | + continue; 55 | + } 56 | + 57 | + holder.broadcastChanges(holder.getFullChunkNowUnchecked()); // LevelChunks are NEVER unloaded 58 | + if (holder.needsBroadcastChanges()) { 59 | + // I DON'T want to KNOW what DUMB plugins might be doing. 60 | + needsChangeBroadcasting.add(holder); 61 | + } 62 | + } 63 | + } 64 | + } 65 | +} 66 | diff --git a/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChunkTicker.java b/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChunkTicker.java 67 | index 858bf20d73d787b6b91e75be7a45f15a6df82f7b..d5f0acade30d8f64a2db2c3aba195b89fd52643b 100644 68 | --- a/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChunkTicker.java 69 | +++ b/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChunkTicker.java 70 | @@ -62,6 +62,8 @@ public class ShreddedPaperChunkTicker { 71 | throw new IllegalStateException("Ticking region " + level.convertable.getLevelId() + " " + region.getRegionPos() + " outside of ShreddedPaperTickThread!"); 72 | } 73 | 74 | + ShreddedPaperChangesBroadcaster.setAsWorkerThread(); 75 | + 76 | while (region.getInternalTaskQueue().executeTask()) ; 77 | 78 | level.chunkTaskScheduler.chunkHolderManager.processUnloads(region); 79 | @@ -90,6 +92,8 @@ public class ShreddedPaperChunkTicker { 80 | 81 | while (region.getInternalTaskQueue().executeTask()) ; 82 | 83 | + ShreddedPaperChangesBroadcaster.broadcastChanges(); 84 | + 85 | if (region.isEmpty()) { 86 | level.chunkSource.tickingRegions.remove(region.getRegionPos()); 87 | } 88 | @@ -99,6 +103,8 @@ public class ShreddedPaperChunkTicker { 89 | } 90 | 91 | private static void _tickChunk(ServerLevel level, LevelChunk chunk1, NaturalSpawner.SpawnState spawnercreature_d) { 92 | + if (chunk1.getChunkHolder().vanillaChunkHolder.needsBroadcastChanges()) ShreddedPaperChangesBroadcaster.add(chunk1.getChunkHolder().vanillaChunkHolder); // ShreddedPaper 93 | + 94 | // Start - Import the same variables as the original chunk ticking method to make copying new changes easier 95 | int j = 1; // Inhabited time increment in ticks 96 | boolean flag = level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !level.players().isEmpty(); // Should run mob spawning code 97 | diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java 98 | index 0e0c3a010d5cfaa0c15bcb38c4e3b1a8d7ad39a1..8da6278f841e0ac032ae74ed75b7689d43e2cdfb 100644 99 | --- a/src/main/java/net/minecraft/server/level/ChunkHolder.java 100 | +++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java 101 | @@ -2,6 +2,7 @@ package net.minecraft.server.level; 102 | 103 | import com.mojang.datafixers.util.Pair; 104 | import io.multipaper.shreddedpaper.region.RegionPos; 105 | +import io.multipaper.shreddedpaper.threading.ShreddedPaperChangesBroadcaster; 106 | import io.papermc.paper.util.TickThread; 107 | import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; 108 | import it.unimi.dsi.fastutil.shorts.ShortSet; 109 | @@ -81,7 +82,7 @@ public class ChunkHolder { 110 | public void onChunkAdd() { 111 | // Paper start - optimise chunk tick iteration 112 | if (this.needsBroadcastChanges()) { 113 | - this.chunkMap.needsChangeBroadcasting.add(this); 114 | + ShreddedPaperChangesBroadcaster.add(this); // this.chunkMap.needsChangeBroadcasting.add(this); // ShreddedPaper - handled by the regions 115 | } 116 | // Paper end - optimise chunk tick iteration 117 | } 118 | @@ -89,7 +90,7 @@ public class ChunkHolder { 119 | public void onChunkRemove() { 120 | // Paper start - optimise chunk tick iteration 121 | if (this.needsBroadcastChanges()) { 122 | - this.chunkMap.needsChangeBroadcasting.remove(this); 123 | + ShreddedPaperChangesBroadcaster.remove(this); // this.chunkMap.needsChangeBroadcasting.remove(this); // ShreddedPaper - handled by the regions 124 | } 125 | // Paper end - optimise chunk tick iteration 126 | } 127 | @@ -275,7 +276,7 @@ public class ChunkHolder { 128 | 129 | private void addToBroadcastMap() { 130 | io.papermc.paper.util.TickThread.ensureTickThread(this.chunkMap.level, this.pos, "Asynchronous ChunkHolder update is not allowed"); 131 | - this.chunkMap.needsChangeBroadcasting.add(this); 132 | + ShreddedPaperChangesBroadcaster.add(this); // this.chunkMap.needsChangeBroadcasting.add(this); // ShreddedPaper - handled by the regions 133 | } 134 | // Paper end - optimise chunk tick iteration 135 | 136 | diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java 137 | index 4b54afb0e543c12583aa2514fc2e2a0378cd0ae1..806c1ab972c81b519b95aa48bc4cde2ef4c482d6 100644 138 | --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java 139 | +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java 140 | @@ -15,6 +15,7 @@ import java.util.function.Supplier; 141 | import javax.annotation.Nullable; 142 | 143 | import io.multipaper.shreddedpaper.region.RegionPos; 144 | +import io.multipaper.shreddedpaper.threading.ShreddedPaperChangesBroadcaster; 145 | import io.papermc.paper.util.TickThread; 146 | import net.minecraft.Util; 147 | import net.minecraft.core.BlockPos; 148 | @@ -678,17 +679,19 @@ public class ServerChunkCache extends ChunkSource { 149 | // Paper - optimise chunk tick iteration 150 | //this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing // Purpur 151 | // Paper start - optimise chunk tick iteration 152 | - if (!this.chunkMap.needsChangeBroadcasting.isEmpty()) { 153 | - it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet copy = this.chunkMap.needsChangeBroadcasting.clone(); 154 | - this.chunkMap.needsChangeBroadcasting.clear(); 155 | - for (ChunkHolder holder : copy) { 156 | - holder.broadcastChanges(holder.getFullChunkNowUnchecked()); // LevelChunks are NEVER unloaded 157 | - if (holder.needsBroadcastChanges()) { 158 | - // I DON'T want to KNOW what DUMB plugins might be doing. 159 | - this.chunkMap.needsChangeBroadcasting.add(holder); 160 | - } 161 | - } 162 | - } 163 | + // ShreddedPaper start - // ShreddedPaper - handled in the region 164 | +// if (!this.chunkMap.needsChangeBroadcasting.isEmpty()) { 165 | +// it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet copy = this.chunkMap.needsChangeBroadcasting.clone(); 166 | +// this.chunkMap.needsChangeBroadcasting.clear(); 167 | +// for (ChunkHolder holder : copy) { 168 | +// holder.broadcastChanges(holder.getFullChunkNowUnchecked()); // LevelChunks are NEVER unloaded 169 | +// if (holder.needsBroadcastChanges()) { 170 | +// // I DON'T want to KNOW what DUMB plugins might be doing. 171 | +// this.chunkMap.needsChangeBroadcasting.add(holder); 172 | +// } 173 | +// } 174 | +// } 175 | + // ShreddedPaper end - handled in the region 176 | // Paper end - optimise chunk tick iteration 177 | //this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing // Purpur 178 | // Paper - optimise chunk tick iteration 179 | diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java 180 | index 7424246750d6ceca1acd5d9ebfd48f0d66504c5d..0a1e3784a26c5b0058b80455e0b0a357d4f25e91 100644 181 | --- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java 182 | +++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java 183 | @@ -3,6 +3,9 @@ package net.minecraft.server.level; 184 | import com.mojang.logging.LogUtils; 185 | import java.util.Objects; 186 | import javax.annotation.Nullable; 187 | + 188 | +import io.multipaper.shreddedpaper.region.RegionPos; 189 | +import io.multipaper.shreddedpaper.threading.ShreddedPaperChunkTicker; 190 | import net.minecraft.advancements.CriteriaTriggers; 191 | import net.minecraft.core.BlockPos; 192 | import net.minecraft.core.Direction; 193 | @@ -26,6 +29,7 @@ import net.minecraft.world.level.block.TrapDoorBlock; 194 | import net.minecraft.world.level.block.entity.BlockEntity; 195 | import net.minecraft.world.level.block.state.BlockState; 196 | import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; 197 | +import net.minecraft.world.level.chunk.LevelChunk; 198 | import net.minecraft.world.phys.BlockHitResult; 199 | import org.slf4j.Logger; 200 | 201 | @@ -467,6 +471,13 @@ public class ServerPlayerGameMode { 202 | } 203 | // Paper end - Trigger bee_nest_destroyed trigger in the correct place 204 | 205 | + // ShreddedPaper start - broadcast block changes immediately if on the wrong thread 206 | + LevelChunk levelChunk = this.level.getChunkIfLoaded(pos); 207 | + if (levelChunk != null) { 208 | + levelChunk.getChunkHolder().vanillaChunkHolder.broadcastChanges(levelChunk); 209 | + } 210 | + // ShreddedPaper end - broadcast block changes immediately if on the wrong thread 211 | + 212 | return true; 213 | // CraftBukkit end 214 | } 215 | -------------------------------------------------------------------------------- /patches/server/0031-Ender-dragon-fight.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Tue, 4 Jun 2024 00:58:04 +0900 4 | Subject: [PATCH] Ender dragon fight 5 | 6 | 7 | diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java 8 | index 18a1b4325cac81b040596071dab99ef9bf6f3142..40df5dbe88419e81e36998120381387eff4f1921 100644 9 | --- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java 10 | +++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java 11 | @@ -9,6 +9,7 @@ import com.google.common.collect.Sets; 12 | import com.mojang.logging.LogUtils; 13 | import com.mojang.serialization.Codec; 14 | import com.mojang.serialization.codecs.RecordCodecBuilder; 15 | +import io.multipaper.shreddedpaper.region.RegionPos; 16 | import it.unimi.dsi.fastutil.objects.ObjectArrayList; 17 | import java.util.Collection; 18 | import java.util.Iterator; 19 | @@ -151,6 +152,7 @@ public class EndDragonFight { 20 | } 21 | 22 | if (!this.dragonEvent.getPlayers().isEmpty()) { 23 | + Runnable r = () -> { // ShreddedPaper - run on end island thread 24 | this.level.getChunkSource().addRegionTicket(TicketType.DRAGON, new ChunkPos(0, 0), 9, Unit.INSTANCE); 25 | boolean flag = this.isArenaLoaded(); 26 | 27 | @@ -179,6 +181,13 @@ public class EndDragonFight { 28 | this.ticksSinceCrystalsScanned = 0; 29 | } 30 | } 31 | + // ShreddedPaper start - run on end island thread 32 | + }; 33 | + while (!this.level.chunkScheduler.getRegionLocker().tryLockNow(RegionPos.forChunk(0, 0), r)) { 34 | + long startBlockingTime = System.nanoTime(); 35 | + this.level.chunkSource.mainThreadProcessor.managedBlock(() -> System.nanoTime() - startBlockingTime < 1_000_000); // Wait for 1ms 36 | + } 37 | + // ShreddedPaper end - run on end island thread 38 | } else { 39 | this.level.getChunkSource().removeRegionTicket(TicketType.DRAGON, new ChunkPos(0, 0), 9, Unit.INSTANCE); 40 | } 41 | -------------------------------------------------------------------------------- /patches/server/0032-End-Gateways.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Wed, 5 Jun 2024 00:49:15 +0900 4 | Subject: [PATCH] End Gateways 5 | 6 | 7 | diff --git a/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java 8 | index fb9611866671880fc7a1a969da928b8f2ad15269..e7ced465076195f1c774a2f5ddd690162f5d50e3 100644 9 | --- a/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java 10 | +++ b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java 11 | @@ -3,7 +3,11 @@ package net.minecraft.world.level.block.entity; 12 | import com.mojang.logging.LogUtils; 13 | import java.util.Iterator; 14 | import java.util.List; 15 | +import java.util.concurrent.CompletableFuture; 16 | import javax.annotation.Nullable; 17 | + 18 | +import io.multipaper.shreddedpaper.region.RegionPos; 19 | +import io.multipaper.shreddedpaper.threading.ShreddedPaperRegionScheduler; 20 | import net.minecraft.advancements.CriteriaTriggers; 21 | import net.minecraft.core.BlockPos; 22 | import net.minecraft.core.Direction; 23 | @@ -177,17 +181,22 @@ public class TheEndGatewayBlockEntity extends TheEndPortalBlockEntity { 24 | } 25 | // Purpur end 26 | blockEntity.teleportCooldown = 100; 27 | - BlockPos blockposition1; 28 | + CompletableFuture blockpositionfuture = CompletableFuture.completedFuture(blockEntity.exitPortal); // ShreddedPaper 29 | 30 | if (blockEntity.exitPortal == null && world.getTypeKey() == LevelStem.END) { // CraftBukkit - work in alternate worlds 31 | - blockposition1 = TheEndGatewayBlockEntity.findOrCreateValidTeleportPos(worldserver, pos); 32 | - blockposition1 = blockposition1.above(10); 33 | + blockpositionfuture = TheEndGatewayBlockEntity.findOrCreateValidTeleportPos(worldserver, pos); // ShreddedPaper 34 | + blockpositionfuture = blockpositionfuture.thenApply(blockposition1 -> blockposition1.above(10)); // ShreddedPaper 35 | + blockpositionfuture.thenAccept(blockposition1 -> { // ShreddedPaper 36 | TheEndGatewayBlockEntity.LOGGER.debug("Creating portal at {}", blockposition1); 37 | TheEndGatewayBlockEntity.spawnGatewayPortal(worldserver, blockposition1, EndGatewayConfiguration.knownExit(pos, false)); 38 | blockEntity.exitPortal = blockposition1; 39 | + }); // ShreddedPaper 40 | } 41 | 42 | - if (blockEntity.exitPortal != null) { 43 | + blockpositionfuture.thenRun(() -> { // ShreddedPaper 44 | + if (blockEntity.exitPortal == null) return; // ShreddedPaper 45 | + ShreddedPaperRegionScheduler.scheduleAcrossLevels(worldserver, RegionPos.forChunk(entity.chunkPosition()), worldserver, RegionPos.forBlockPos(blockEntity.exitPortal), () -> { // ShreddedPaper 46 | + BlockPos blockposition1; // ShreddedPaper 47 | blockposition1 = blockEntity.exactTeleport ? blockEntity.exitPortal : TheEndGatewayBlockEntity.findExitPosition(world, blockEntity.exitPortal); 48 | Entity entity1; 49 | 50 | @@ -243,7 +252,8 @@ public class TheEndGatewayBlockEntity extends TheEndPortalBlockEntity { 51 | entity1.setPortalCooldown(); 52 | entity1.teleportToWithTicket(teleEvent.getTo().getX(), teleEvent.getTo().getY(), teleEvent.getTo().getZ()); 53 | // CraftBukkit end 54 | - } 55 | + }); // ShreddedPaper 56 | + }); // ShreddedPaper 57 | 58 | TheEndGatewayBlockEntity.triggerCooldown(world, pos, state, blockEntity); 59 | } 60 | @@ -256,8 +266,9 @@ public class TheEndGatewayBlockEntity extends TheEndPortalBlockEntity { 61 | return blockposition1.above(); 62 | } 63 | 64 | - private static BlockPos findOrCreateValidTeleportPos(ServerLevel world, BlockPos pos) { 65 | + private static CompletableFuture findOrCreateValidTeleportPos(ServerLevel world, BlockPos pos) { // ShreddedPaper 66 | Vec3 vec3d = TheEndGatewayBlockEntity.findExitPortalXZPosTentative(world, pos); 67 | + return world.getWorld().getChunkAtAsyncUrgently((int) vec3d.x >> 4, (int) vec3d.z >> 4).thenApply(bukkitChunk -> { // ShreddedPaper - run on region's thread for write access 68 | LevelChunk chunk = TheEndGatewayBlockEntity.getChunk(world, vec3d); 69 | BlockPos blockposition1 = TheEndGatewayBlockEntity.findValidSpawnInChunk(chunk); 70 | 71 | @@ -276,6 +287,7 @@ public class TheEndGatewayBlockEntity extends TheEndPortalBlockEntity { 72 | } 73 | 74 | return TheEndGatewayBlockEntity.findTallestBlock(world, blockposition1, 16, true); 75 | + }); // ShreddedPaper 76 | } 77 | 78 | private static Vec3 findExitPortalXZPosTentative(ServerLevel world, BlockPos pos) { 79 | -------------------------------------------------------------------------------- /patches/server/0033-Block-events.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Wed, 5 Jun 2024 20:01:02 +0900 4 | Subject: [PATCH] Block events 5 | 6 | 7 | diff --git a/src/main/java/io/multipaper/shreddedpaper/region/LevelChunkRegion.java b/src/main/java/io/multipaper/shreddedpaper/region/LevelChunkRegion.java 8 | index 43bd6b7780226afadd196687dfb3d398b0f610cc..e40ee452a89a5fc60bb75c74631de2e9b0e6a730 100644 9 | --- a/src/main/java/io/multipaper/shreddedpaper/region/LevelChunkRegion.java 10 | +++ b/src/main/java/io/multipaper/shreddedpaper/region/LevelChunkRegion.java 11 | @@ -4,20 +4,25 @@ import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadedTaskQu 12 | import io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet; 13 | import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; 14 | import it.unimi.dsi.fastutil.objects.ObjectArrayList; 15 | +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; 16 | import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; 17 | import it.unimi.dsi.fastutil.objects.ReferenceArrayList; 18 | import net.minecraft.server.level.ServerLevel; 19 | import net.minecraft.server.level.ServerPlayer; 20 | import net.minecraft.world.entity.Entity; 21 | import net.minecraft.world.entity.Mob; 22 | +import net.minecraft.world.level.BlockEventData; 23 | import net.minecraft.world.level.block.entity.TickingBlockEntity; 24 | import net.minecraft.world.level.chunk.LevelChunk; 25 | 26 | import java.util.ArrayList; 27 | +import java.util.Collection; 28 | import java.util.List; 29 | import java.util.Set; 30 | import java.util.concurrent.ConcurrentLinkedQueue; 31 | import java.util.function.Consumer; 32 | +import java.util.function.Predicate; 33 | +import java.util.function.Supplier; 34 | 35 | public class LevelChunkRegion { 36 | 37 | @@ -33,6 +38,7 @@ public class LevelChunkRegion { 38 | public final List tickingBlockEntities = new ReferenceArrayList<>(); 39 | public final List pendingBlockEntityTickers = new ReferenceArrayList<>(); 40 | private final ObjectOpenHashSet navigatingMobs = new ObjectOpenHashSet<>(); 41 | + private final ObjectLinkedOpenHashSet blockEvents = new ObjectLinkedOpenHashSet<>(); 42 | 43 | public LevelChunkRegion(ServerLevel level, RegionPos regionPos) { 44 | this.level = level; 45 | @@ -161,6 +167,26 @@ public class LevelChunkRegion { 46 | toRun.forEach(DelayedTask::run); 47 | } 48 | 49 | + public synchronized void addBlockEvent(BlockEventData blockEvent) { 50 | + this.blockEvents.add(blockEvent); 51 | + } 52 | + 53 | + public synchronized void addAllBlockEvents(Collection blockEvents) { 54 | + this.blockEvents.addAll(blockEvents); 55 | + } 56 | + 57 | + public boolean hasBlockEvents() { 58 | + return !this.blockEvents.isEmpty(); 59 | + } 60 | + 61 | + public synchronized BlockEventData removeFirstBlockEvent() { 62 | + return this.blockEvents.removeFirst(); 63 | + } 64 | + 65 | + public synchronized void removeBlockEventsIf(Predicate predicate) { 66 | + this.blockEvents.removeIf(predicate); 67 | + } 68 | + 69 | public boolean isEmpty() { 70 | return levelChunks.isEmpty() 71 | && tickingEntities.size() == 0 72 | @@ -172,6 +198,7 @@ public class LevelChunkRegion { 73 | && pendingBlockEntityTickers.isEmpty() 74 | && trackedEntities.isEmpty() 75 | && navigatingMobs.isEmpty() 76 | + && blockEvents.isEmpty() 77 | ; 78 | } 79 | } 80 | diff --git a/src/main/java/io/multipaper/shreddedpaper/region/LevelChunkRegionMap.java b/src/main/java/io/multipaper/shreddedpaper/region/LevelChunkRegionMap.java 81 | index 80acf2a25921c7b674d0758c0c29a7352746726b..2b88bc33446fdb6fbbdaf0ac170a9ac215df6828 100644 82 | --- a/src/main/java/io/multipaper/shreddedpaper/region/LevelChunkRegionMap.java 83 | +++ b/src/main/java/io/multipaper/shreddedpaper/region/LevelChunkRegionMap.java 84 | @@ -6,10 +6,12 @@ import net.minecraft.server.level.ServerLevel; 85 | import net.minecraft.server.level.ServerPlayer; 86 | import net.minecraft.world.entity.Entity; 87 | import net.minecraft.world.entity.Mob; 88 | +import net.minecraft.world.level.BlockEventData; 89 | import net.minecraft.world.level.ChunkPos; 90 | import net.minecraft.world.level.chunk.LevelChunk; 91 | import io.multipaper.shreddedpaper.threading.ShreddedPaperRegionLocker; 92 | import io.multipaper.shreddedpaper.util.SimpleStampedLock; 93 | +import net.minecraft.world.level.levelgen.structure.BoundingBox; 94 | 95 | import java.util.ArrayList; 96 | import java.util.List; 97 | @@ -199,6 +201,24 @@ public class LevelChunkRegionMap { 98 | } 99 | } 100 | 101 | + public void addBlockEvent(BlockEventData blockEvent) { 102 | + getOrCreate(RegionPos.forBlockPos(blockEvent.pos())).addBlockEvent(blockEvent); 103 | + } 104 | + 105 | + public void forEachRegionInBoundingBox(BoundingBox box, Consumer consumer) { 106 | + RegionPos minPos = RegionPos.forBlockPos(box.minX(), box.minZ(), box.minZ()); 107 | + RegionPos maxPos = RegionPos.forBlockPos(box.maxX(), box.maxZ(), box.maxZ()); 108 | + 109 | + for (int x = minPos.x; x <= maxPos.x; x++) { 110 | + for (int z = minPos.z; z <= maxPos.z; z++) { 111 | + LevelChunkRegion region = get(new RegionPos(x, z)); 112 | + if (region != null) { 113 | + consumer.accept(region); 114 | + } 115 | + } 116 | + } 117 | + } 118 | + 119 | public List collectRelevantNavigatingMobs(RegionPos regionPos) { 120 | if (!level.chunkScheduler.getRegionLocker().hasLock(regionPos)) { 121 | // We care about the navigating mobs in at least this region, ensure it's locked 122 | diff --git a/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChunkTicker.java b/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChunkTicker.java 123 | index d5f0acade30d8f64a2db2c3aba195b89fd52643b..f32aecd69d929c005ca8226054c218c7dd37e6fc 100644 124 | --- a/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChunkTicker.java 125 | +++ b/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChunkTicker.java 126 | @@ -82,6 +82,8 @@ public class ShreddedPaperChunkTicker { 127 | 128 | region.forEach(chunk -> _tickChunk(level, chunk, spawnercreature_d)); 129 | 130 | + level.runBlockEvents(region); 131 | + 132 | region.forEachTickingEntity(ShreddedPaperEntityTicker::tickEntity); 133 | 134 | region.forEachTrackedEntity(ShreddedPaperEntityTicker::processTrackQueue); 135 | diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java 136 | index e2fe80d4253c783828f64eb38d293e0e2d30d5f7..ef8c4455c06cd0eca58d21ac153acdc6f6999959 100644 137 | --- a/src/main/java/net/minecraft/server/level/ServerLevel.java 138 | +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java 139 | @@ -6,6 +6,7 @@ import com.google.common.collect.Lists; 140 | import com.mojang.datafixers.DataFixer; 141 | import com.mojang.datafixers.util.Pair; 142 | import com.mojang.logging.LogUtils; 143 | +import io.multipaper.shreddedpaper.region.LevelChunkRegion; 144 | import io.papermc.paper.util.TickThread; 145 | import it.unimi.dsi.fastutil.ints.Int2ObjectMap; 146 | import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; 147 | @@ -15,7 +16,6 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry; 148 | import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; 149 | import it.unimi.dsi.fastutil.objects.ObjectArrayList; 150 | import it.unimi.dsi.fastutil.objects.ObjectIterator; 151 | -import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; 152 | import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; 153 | import java.io.BufferedWriter; 154 | import java.io.IOException; 155 | @@ -125,7 +125,6 @@ import net.minecraft.world.level.biome.BiomeSource; 156 | import net.minecraft.world.level.block.Block; 157 | import net.minecraft.world.level.block.Blocks; 158 | import net.minecraft.world.level.block.SnowLayerBlock; 159 | -import net.minecraft.world.level.block.entity.TickingBlockEntity; 160 | import net.minecraft.world.level.block.state.BlockState; 161 | import net.minecraft.world.level.chunk.ChunkAccess; 162 | import net.minecraft.world.level.chunk.ChunkGenerator; 163 | @@ -136,7 +135,6 @@ import net.minecraft.world.level.chunk.storage.RegionStorageInfo; 164 | import net.minecraft.world.level.dimension.BuiltinDimensionTypes; 165 | import net.minecraft.world.level.dimension.LevelStem; 166 | import net.minecraft.world.level.dimension.end.EndDragonFight; 167 | -import net.minecraft.world.level.entity.EntityTickList; 168 | import net.minecraft.world.level.entity.EntityTypeTest; 169 | import net.minecraft.world.level.entity.LevelCallback; 170 | import net.minecraft.world.level.entity.LevelEntityGetter; 171 | @@ -209,8 +207,8 @@ public class ServerLevel extends Level implements WorldGenLevel { 172 | final Set navigatingMobs; 173 | final ThreadLocal isUpdatingNavigations = ThreadLocal.withInitial(() -> false); // ShreddedPaper - make thread local 174 | protected final Raids raids; 175 | - private final ObjectLinkedOpenHashSet blockEvents; 176 | - private final List blockEventsToReschedule; 177 | + // private final ObjectLinkedOpenHashSet blockEvents; // ShreddedPaper - moved into each region 178 | + private final ThreadLocal> blockEventsToRescheduleThreadLocal; // ShreddedPaper 179 | private boolean handlingTick; 180 | private final List customSpawners; 181 | @Nullable 182 | @@ -717,8 +715,8 @@ public class ServerLevel extends Level implements WorldGenLevel { 183 | this.fluidTicks = new LevelTicksRegionProxy<>(this::isPositionTickingWithEntitiesLoaded, this.getProfilerSupplier()); // ShreddedPaper 184 | this.pathTypesByPosCache = new PathTypeCache(); 185 | this.navigatingMobs = new ObjectOpenHashSet(); 186 | - this.blockEvents = new ObjectLinkedOpenHashSet(); 187 | - this.blockEventsToReschedule = new ArrayList(64); 188 | + // this.blockEvents = new ObjectLinkedOpenHashSet(); // ShreddedPaper - moved into each region 189 | + this.blockEventsToRescheduleThreadLocal = ThreadLocal.withInitial(() -> new ArrayList(64)); // ShreddedPaper 190 | this.dragonParts = new Int2ObjectOpenHashMap(); 191 | this.tickTime = flag1; 192 | this.server = minecraftserver; 193 | @@ -903,7 +901,7 @@ public class ServerLevel extends Level implements WorldGenLevel { 194 | //gameprofilerfiller.popPush("blockEvents"); // Purpur 195 | if (flag) { 196 | // this.timings.doSounds.startTiming(); // Spigot // Purpur 197 | - this.runBlockEvents(); 198 | + // this.runBlockEvents(); // ShreddedPaper - handled locally in the region 199 | // this.timings.doSounds.stopTiming(); // Spigot // Purpur 200 | } 201 | 202 | @@ -2044,25 +2042,26 @@ public class ServerLevel extends Level implements WorldGenLevel { 203 | 204 | @Override 205 | public void blockEvent(BlockPos pos, Block block, int type, int data) { 206 | - this.blockEvents.add(new BlockEventData(pos, block, type, data)); 207 | + this.chunkSource.tickingRegions.addBlockEvent(new BlockEventData(pos, block, type, data)); // ShreddedPaper 208 | } 209 | 210 | - private void runBlockEvents() { 211 | - this.blockEventsToReschedule.clear(); 212 | + public void runBlockEvents(LevelChunkRegion region) { // ShreddedPaper 213 | + List blockEventsToReschedule = blockEventsToRescheduleThreadLocal.get(); // ShreddedPaper 214 | + blockEventsToReschedule.clear(); // ShreddedPaper 215 | 216 | - while (!this.blockEvents.isEmpty()) { 217 | - BlockEventData blockactiondata = (BlockEventData) this.blockEvents.removeFirst(); 218 | + while (region.hasBlockEvents()) { // ShreddedPaper 219 | + BlockEventData blockactiondata = (BlockEventData) region.removeFirstBlockEvent(); // ShreddedPaper 220 | 221 | if (this.shouldTickBlocksAt(blockactiondata.pos())) { 222 | if (this.doBlockEvent(blockactiondata)) { 223 | this.server.getPlayerList().broadcast((Player) null, (double) blockactiondata.pos().getX(), (double) blockactiondata.pos().getY(), (double) blockactiondata.pos().getZ(), 64.0D, this.dimension(), new ClientboundBlockEventPacket(blockactiondata.pos(), blockactiondata.block(), blockactiondata.paramA(), blockactiondata.paramB())); 224 | } 225 | } else { 226 | - this.blockEventsToReschedule.add(blockactiondata); 227 | + blockEventsToReschedule.add(blockactiondata); // ShreddedPaper 228 | } 229 | } 230 | 231 | - this.blockEvents.addAll(this.blockEventsToReschedule); 232 | + region.addAllBlockEvents(blockEventsToReschedule); // ShreddedPaper 233 | } 234 | 235 | private boolean doBlockEvent(BlockEventData event) { 236 | @@ -2546,9 +2545,13 @@ public class ServerLevel extends Level implements WorldGenLevel { 237 | 238 | @VisibleForTesting 239 | public void clearBlockEvents(BoundingBox box) { 240 | - this.blockEvents.removeIf((blockactiondata) -> { 241 | - return box.isInside(blockactiondata.pos()); 242 | + // ShreddedPaper start - moved blockEvents into regions 243 | + this.getLevel().getChunkSource().tickingRegions.forEachRegionInBoundingBox(box, region -> { 244 | + region.removeBlockEventsIf((blockactiondata) -> { 245 | + return box.isInside(blockactiondata.pos()); 246 | + }); 247 | }); 248 | + // ShreddedPaper end - moved blockEvents into regions 249 | } 250 | 251 | @Override 252 | -------------------------------------------------------------------------------- /patches/server/0034-Add-various-API-for-Folia-plugin-compatibility.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: ProdPreva1l 3 | Date: Wed, 5 Jun 2024 20:27:53 +1000 4 | Subject: [PATCH] Add various API for Folia plugin compatibility 5 | 6 | 7 | diff --git a/src/main/java/io/papermc/paper/threadedregions/RegionizedServer.java b/src/main/java/io/papermc/paper/threadedregions/RegionizedServer.java 8 | new file mode 100644 9 | index 0000000000000000000000000000000000000000..69db3090c24ab058549cdd8e78ca1beb7be5e7a8 10 | --- /dev/null 11 | +++ b/src/main/java/io/papermc/paper/threadedregions/RegionizedServer.java 12 | @@ -0,0 +1,7 @@ 13 | +package io.papermc.paper.threadedregions; 14 | + 15 | +@SuppressWarnings("unused") 16 | +public class RegionizedServer { 17 | + /* We have nothing here because this is just to fix compat 18 | + for Folia plugins that have hard coded checks for Folia */ 19 | +} 20 | diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java 21 | index ade6396885d63991640dbee0f7d1a56b02d22397..c11924979735e79655a2a0ae5485cd6ef74555b1 100644 22 | --- a/src/main/java/net/minecraft/server/MinecraftServer.java 23 | +++ b/src/main/java/net/minecraft/server/MinecraftServer.java 24 | @@ -16,6 +16,7 @@ import com.mojang.datafixers.DataFixer; 25 | import com.mojang.logging.LogUtils; 26 | import io.multipaper.shreddedpaper.ShreddedPaper; 27 | import io.multipaper.shreddedpaper.threading.ShreddedPaperPlayerTicker; 28 | +import io.papermc.paper.threadedregions.RegionizedServerInitEvent; 29 | import io.papermc.paper.util.TickThread; 30 | import it.unimi.dsi.fastutil.longs.LongIterator; 31 | import it.unimi.dsi.fastutil.objects.ObjectArrayList; 32 | @@ -1161,6 +1162,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 3 | Date: Thu, 6 Jun 2024 17:09:45 +0900 4 | Subject: [PATCH] 5 | allow-unsupported-plugins-to-modify-chunks-via-global-scheduler 6 | 7 | 8 | diff --git a/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChunkTicker.java b/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChunkTicker.java 9 | index f32aecd69d929c005ca8226054c218c7dd37e6fc..3ebb70ecb854ba0172148e48a07578dc9c90d853 100644 10 | --- a/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChunkTicker.java 11 | +++ b/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChunkTicker.java 12 | @@ -21,11 +21,15 @@ import java.util.concurrent.CompletableFuture; 13 | 14 | public class ShreddedPaperChunkTicker { 15 | 16 | + public static boolean tickingChunks = false; 17 | + 18 | public static void tickChunks(ServerLevel level, NaturalSpawner.SpawnState spawnercreature_d) { 19 | List> futures = new ArrayList<>(); 20 | 21 | MinecraftServer.getServer().executeMidTickTasks(); 22 | 23 | + tickingChunks = true; 24 | + 25 | level.chunkSource.tickingRegions.forEach( 26 | region -> futures.add(tickRegion(level, region, spawnercreature_d)) 27 | ); 28 | @@ -36,6 +40,8 @@ public class ShreddedPaperChunkTicker { 29 | } 30 | } 31 | 32 | + tickingChunks = false; 33 | + 34 | MinecraftServer.getServer().executeMidTickTasks(); 35 | } 36 | 37 | diff --git a/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperRegionLocker.java b/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperRegionLocker.java 38 | index cee763e641821090382ab1d20405d54052852bab..39262659b6b0793880378fb49220c30de3d78485 100644 39 | --- a/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperRegionLocker.java 40 | +++ b/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperRegionLocker.java 41 | @@ -1,5 +1,6 @@ 42 | package io.multipaper.shreddedpaper.threading; 43 | 44 | +import io.papermc.paper.util.TickThread; 45 | import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; 46 | import io.multipaper.shreddedpaper.region.RegionPos; 47 | import io.multipaper.shreddedpaper.util.ObjectHolder; 48 | @@ -37,7 +38,7 @@ public class ShreddedPaperRegionLocker { 49 | * Checks if the current thread holds a read lock for the given region 50 | */ 51 | public boolean hasLock(RegionPos regionPos) { 52 | - return localLocks.get().contains(regionPos); 53 | + return localLocks.get().contains(regionPos) || TickThread.canBypassTickThreadCheck(); 54 | } 55 | 56 | /** 57 | @@ -46,7 +47,7 @@ public class ShreddedPaperRegionLocker { 58 | * syncing conflicts with other servers. 59 | */ 60 | public boolean hasWriteLock(RegionPos regionPos) { 61 | - return writeLocks.get().contains(regionPos); 62 | + return writeLocks.get().contains(regionPos) || TickThread.canBypassTickThreadCheck(); 63 | } 64 | 65 | /** 66 | diff --git a/src/main/java/io/papermc/paper/util/TickThread.java b/src/main/java/io/papermc/paper/util/TickThread.java 67 | index 313f30948c9ebd09094c1623bfe2c3594f30077c..2cc58dacd64689415b9fe1e35ff8d23ad103f5fd 100644 68 | --- a/src/main/java/io/papermc/paper/util/TickThread.java 69 | +++ b/src/main/java/io/papermc/paper/util/TickThread.java 70 | @@ -1,6 +1,9 @@ 71 | package io.papermc.paper.util; 72 | 73 | +import io.multipaper.shreddedpaper.config.ShreddedPaperConfiguration; 74 | +import io.multipaper.shreddedpaper.threading.ShreddedPaperChunkTicker; 75 | import io.multipaper.shreddedpaper.threading.ShreddedPaperTickThread; 76 | +import io.multipaper.shreddedpaper.threading.SynchronousPluginExecution; 77 | import net.minecraft.core.BlockPos; 78 | import net.minecraft.server.MinecraftServer; 79 | import net.minecraft.server.level.ServerLevel; 80 | @@ -69,6 +72,16 @@ public class TickThread extends Thread { 81 | } 82 | // ShreddedPaper end 83 | 84 | + // ShreddedPaper start 85 | + public static boolean canBypassTickThreadCheck() { 86 | + return 87 | + ShreddedPaperConfiguration.get().multithreading.allowUnsupportedPluginsToModifyChunksViaGlobalScheduler && 88 | + SynchronousPluginExecution.getCurrentPlugin() != null && 89 | + Thread.currentThread() == MinecraftServer.getServer().getRunningThread() && 90 | + !ShreddedPaperChunkTicker.tickingChunks; 91 | + } 92 | + // ShreddedPaper end 93 | + 94 | /** 95 | * @deprecated 96 | */ 97 | -------------------------------------------------------------------------------- /patches/server/0036-Entity-retirement-debug-log.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Sat, 8 Jun 2024 05:14:47 +0900 4 | Subject: [PATCH] Entity retirement debug log 5 | 6 | 7 | diff --git a/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java b/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java 8 | index 62484ebf4550b05182f693a3180bbac5d5fd906d..9fb58f07997e0c4c336b246e518d7e97eb766ba5 100644 9 | --- a/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java 10 | +++ b/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java 11 | @@ -1,6 +1,7 @@ 12 | package io.papermc.paper.threadedregions; 13 | 14 | import ca.spottedleaf.concurrentutil.util.Validate; 15 | +import com.mojang.logging.LogUtils; 16 | import io.papermc.paper.util.TickThread; 17 | import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; 18 | import net.minecraft.world.entity.Entity; 19 | @@ -40,6 +41,7 @@ public final class EntityScheduler { 20 | private static final record ScheduledTask(Consumer run, Consumer retired) {} 21 | 22 | private long tickCount = 0L; 23 | + private Exception retiredReason; // ShreddedPaper 24 | private static final long RETIRED_TICK_COUNT = -1L; 25 | private final Object stateLock = new Object(); 26 | private final Long2ObjectOpenHashMap> oneTimeDelayed = new Long2ObjectOpenHashMap<>(); 27 | @@ -66,6 +68,7 @@ public final class EntityScheduler { 28 | throw new IllegalStateException("Already retired"); 29 | } 30 | this.tickCount = RETIRED_TICK_COUNT; 31 | + this.retiredReason = new Exception("Retired"); // ShreddedPaper 32 | } 33 | 34 | final Entity thisEntity = this.entity.getHandleRaw(); 35 | @@ -144,6 +147,7 @@ public final class EntityScheduler { 36 | final List toRun; 37 | synchronized (this.stateLock) { 38 | if (this.tickCount == RETIRED_TICK_COUNT) { 39 | + LogUtils.getClassLogger().error("Tried to execute tick on entity, but was retired here: {}", this.entity.getHandle(), retiredReason); // ShreddedPaper 40 | throw new IllegalStateException("Ticking retired scheduler"); 41 | } 42 | ++this.tickCount; 43 | -------------------------------------------------------------------------------- /patches/server/0037-Raids.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Sun, 9 Jun 2024 09:42:44 +0900 4 | Subject: [PATCH] Raids 5 | 6 | 7 | diff --git a/src/main/java/net/minecraft/world/entity/raid/Raid.java b/src/main/java/net/minecraft/world/entity/raid/Raid.java 8 | index fdff9788eaf663be79214b2ca491f0f0444f6136..71a442d218b7e7e7ebb57d9a1559289bda04c4d5 100644 9 | --- a/src/main/java/net/minecraft/world/entity/raid/Raid.java 10 | +++ b/src/main/java/net/minecraft/world/entity/raid/Raid.java 11 | @@ -15,6 +15,8 @@ import java.util.UUID; 12 | import java.util.function.Predicate; 13 | import java.util.stream.Stream; 14 | import javax.annotation.Nullable; 15 | + 16 | +import io.multipaper.shreddedpaper.ShreddedPaper; 17 | import net.minecraft.ChatFormatting; 18 | import net.minecraft.advancements.CriteriaTriggers; 19 | import net.minecraft.core.BlockPos; 20 | @@ -425,14 +427,17 @@ public class Raid { 21 | LivingEntity entityliving = (LivingEntity) entity; 22 | 23 | if (!entity.isSpectator()) { 24 | + ShreddedPaper.ensureSync(entity, () -> { // ShreddedPaper - run on right thread 25 | entityliving.addEffect(new MobEffectInstance(MobEffects.HERO_OF_THE_VILLAGE, 48000, this.raidOmenLevel - 1, false, false, true)); 26 | if (entityliving instanceof ServerPlayer) { 27 | ServerPlayer entityplayer = (ServerPlayer) entityliving; 28 | 29 | entityplayer.awardStat(Stats.RAID_WIN); 30 | CriteriaTriggers.RAID_WIN.trigger(entityplayer); 31 | - winners.add(entityplayer.getBukkitEntity()); // CraftBukkit 32 | + // winners.add(entityplayer.getBukkitEntity()); // CraftBukkit // ShreddedPaper - don't run on right thread 33 | } 34 | + }); // ShreddedPaper - run on right thread 35 | + if (entityliving instanceof ServerPlayer entityplayer) winners.add(entityplayer.getBukkitEntity()); // ShreddedPaper - this doesn't need to be run on right thread 36 | } 37 | } 38 | } 39 | diff --git a/src/main/java/net/minecraft/world/entity/raid/Raids.java b/src/main/java/net/minecraft/world/entity/raid/Raids.java 40 | index eedce2a3d67d875d5174ee125e2679480d4d412c..998d7184cb5718e6f643e6dccc416cb872b9d519 100644 41 | --- a/src/main/java/net/minecraft/world/entity/raid/Raids.java 42 | +++ b/src/main/java/net/minecraft/world/entity/raid/Raids.java 43 | @@ -5,6 +5,8 @@ import java.util.Iterator; 44 | import java.util.List; 45 | import java.util.Map; 46 | import javax.annotation.Nullable; 47 | + 48 | +import io.multipaper.shreddedpaper.ShreddedPaper; 49 | import net.minecraft.core.BlockPos; 50 | import net.minecraft.core.Holder; 51 | import net.minecraft.core.HolderLookup; 52 | @@ -27,7 +29,7 @@ public class Raids extends SavedData { 53 | 54 | private static final String RAID_FILE_ID = "raids"; 55 | public final Map playerCooldowns = Maps.newHashMap(); 56 | - public final Map raidMap = Maps.newHashMap(); 57 | + public final Map raidMap = Maps.newConcurrentMap(); // ShreddedPaper - concurrent map 58 | private final ServerLevel level; 59 | private int nextAvailableID; 60 | private int tick; 61 | @@ -68,16 +70,18 @@ public class Raids extends SavedData { 62 | while (iterator.hasNext()) { 63 | Raid raid = (Raid) iterator.next(); 64 | 65 | + ShreddedPaper.runSync((ServerLevel) raid.getLevel(), raid.getCenter(), () -> { // ShreddedPaper - run on right thread 66 | if (this.level.getGameRules().getBoolean(GameRules.RULE_DISABLE_RAIDS)) { 67 | raid.stop(); 68 | } 69 | 70 | if (raid.isStopped()) { 71 | - iterator.remove(); 72 | + this.raidMap.remove(raid.getId(), raid); // iterator.remove(); // ShreddedPaper - run on right thread 73 | this.setDirty(); 74 | } else { 75 | raid.tick(); 76 | } 77 | + }); // ShreddedPaper - run on right thread 78 | } 79 | 80 | if (this.tick % 200 == 0) { 81 | -------------------------------------------------------------------------------- /patches/server/0038-Thread-safe-alternate-redstone-handler.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Tue, 18 Jun 2024 08:49:24 +0900 4 | Subject: [PATCH] Thread-safe alternate redstone handler 5 | 6 | 7 | diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java 8 | index ef8c4455c06cd0eca58d21ac153acdc6f6999959..a7f3e1334e6df8fa9f2fce374be0df6d60bdc6e5 100644 9 | --- a/src/main/java/net/minecraft/server/level/ServerLevel.java 10 | +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java 11 | @@ -228,7 +228,7 @@ public class ServerLevel extends Level implements WorldGenLevel { 12 | public final UUID uuid; 13 | public boolean hasPhysicsEvent = true; // Paper - BlockPhysicsEvent 14 | public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent 15 | - private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current) 16 | + private final ThreadLocal wireHandler = ThreadLocal.withInitial(() -> new alternate.current.wire.WireHandler(this)); // Paper - optimize redstone (Alternate Current) 17 | public boolean hasRidableMoveEvent = false; // Purpur 18 | 19 | public LevelChunk getChunkIfLoaded(int x, int z) { 20 | @@ -2765,7 +2765,7 @@ public class ServerLevel extends Level implements WorldGenLevel { 21 | // Paper start - optimize redstone (Alternate Current) 22 | @Override 23 | public alternate.current.wire.WireHandler getWireHandler() { 24 | - return wireHandler; 25 | + return wireHandler.get(); 26 | } 27 | // Paper end - optimize redstone (Alternate Current) 28 | 29 | -------------------------------------------------------------------------------- /patches/server/0039-Thread-safe-redstoneUpdateInfos.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Tue, 18 Jun 2024 17:01:01 +0900 4 | Subject: [PATCH] Thread-safe redstoneUpdateInfos 5 | 6 | 7 | diff --git a/src/main/java/io/multipaper/shreddedpaper/region/LevelChunkRegion.java b/src/main/java/io/multipaper/shreddedpaper/region/LevelChunkRegion.java 8 | index 99680a983da4c64b709cae624348992cdc83e7f3..ee00dd2ea0d2143cbc8282a34ad25a08b0bdd1b4 100644 9 | --- a/src/main/java/io/multipaper/shreddedpaper/region/LevelChunkRegion.java 10 | +++ b/src/main/java/io/multipaper/shreddedpaper/region/LevelChunkRegion.java 11 | @@ -12,9 +12,11 @@ import net.minecraft.server.level.ServerPlayer; 12 | import net.minecraft.world.entity.Entity; 13 | import net.minecraft.world.entity.Mob; 14 | import net.minecraft.world.level.BlockEventData; 15 | +import net.minecraft.world.level.block.RedstoneTorchBlock; 16 | import net.minecraft.world.level.block.entity.TickingBlockEntity; 17 | import net.minecraft.world.level.chunk.LevelChunk; 18 | 19 | +import java.util.ArrayDeque; 20 | import java.util.ArrayList; 21 | import java.util.Collection; 22 | import java.util.List; 23 | @@ -39,6 +41,7 @@ public class LevelChunkRegion { 24 | public final List pendingBlockEntityTickers = new ReferenceArrayList<>(); 25 | private final ObjectOpenHashSet navigatingMobs = new ObjectOpenHashSet<>(); 26 | private final ObjectLinkedOpenHashSet blockEvents = new ObjectLinkedOpenHashSet<>(); 27 | + public ArrayDeque redstoneUpdateInfos; 28 | 29 | public LevelChunkRegion(ServerLevel level, RegionPos regionPos) { 30 | this.level = level; 31 | diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java 32 | index 484ba0a90bd6f13fd2116279e422b1c9855d25ea..0af343050a13371b86379a794caeb8dfeeb8550b 100644 33 | --- a/src/main/java/net/minecraft/world/level/Level.java 34 | +++ b/src/main/java/net/minecraft/world/level/Level.java 35 | @@ -186,7 +186,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { 36 | private org.spigotmc.TickLimiter tileLimiter; 37 | // private int tileTickPosition; // ShreddedPaper - removed tileTickPosition 38 | public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions 39 | - public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here 40 | + // public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here // ShreddedPaper - move to LevelChunkRegion 41 | 42 | // Purpur start 43 | private com.google.common.cache.Cache playerBreedingCooldowns; 44 | diff --git a/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java b/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java 45 | index ceba9617748a8b4f3a9bd459475952c9c6c9ed7c..554377cfee5a7b8df3ddd67a043e8b6a20982a35 100644 46 | --- a/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java 47 | +++ b/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java 48 | @@ -6,6 +6,8 @@ import java.util.Iterator; 49 | import java.util.List; 50 | import java.util.Map; 51 | import java.util.WeakHashMap; 52 | + 53 | +import io.multipaper.shreddedpaper.region.RegionPos; 54 | import net.minecraft.core.BlockPos; 55 | import net.minecraft.core.Direction; 56 | import net.minecraft.core.particles.DustParticleOptions; 57 | @@ -81,7 +83,7 @@ public class RedstoneTorchBlock extends BaseTorchBlock { 58 | protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { 59 | boolean flag = this.hasNeighborSignal(world, pos, state); 60 | // Paper start - Faster redstone torch rapid clock removal 61 | - java.util.ArrayDeque redstoneUpdateInfos = world.redstoneUpdateInfos; 62 | + java.util.ArrayDeque redstoneUpdateInfos = world.chunkSource.tickingRegions.get(RegionPos.forBlockPos(pos)).redstoneUpdateInfos; // ShreddedPaper - move redstoneUpdateInfos to the region 63 | if (redstoneUpdateInfos != null) { 64 | RedstoneTorchBlock.Toggle curr; 65 | while ((curr = redstoneUpdateInfos.peek()) != null && world.getGameTime() - curr.when > 60L) { 66 | @@ -165,9 +167,9 @@ public class RedstoneTorchBlock extends BaseTorchBlock { 67 | 68 | private static boolean isToggledTooFrequently(Level world, BlockPos pos, boolean addNew) { 69 | // Paper start - Faster redstone torch rapid clock removal 70 | - java.util.ArrayDeque list = world.redstoneUpdateInfos; 71 | + java.util.ArrayDeque list = ((ServerLevel) world).chunkSource.tickingRegions.get(RegionPos.forBlockPos(pos)).redstoneUpdateInfos; // ShreddedPaper - move redstoneUpdateInfos to the region 72 | if (list == null) { 73 | - list = world.redstoneUpdateInfos = new java.util.ArrayDeque<>(); 74 | + list = ((ServerLevel) world).chunkSource.tickingRegions.get(RegionPos.forBlockPos(pos)).redstoneUpdateInfos = new java.util.ArrayDeque<>(); // ShreddedPaper - move redstoneUpdateInfos to the region 75 | } 76 | // Paper end - Faster redstone torch rapid clock removal 77 | 78 | -------------------------------------------------------------------------------- /patches/server/0040-Thread-safe-chunksBeingWorkedOn.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Thu, 20 Jun 2024 14:56:49 +0900 4 | Subject: [PATCH] Thread-safe chunksBeingWorkedOn 5 | 6 | 7 | diff --git a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java 8 | index 8ef22f8f0d6da49247a90152e5cfa9ffc7f596a4..161b847fe36613a6bc76ed7399ff118a356c05aa 100644 9 | --- a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java 10 | +++ b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java 11 | @@ -5,7 +5,10 @@ import com.mojang.logging.LogUtils; 12 | import it.unimi.dsi.fastutil.objects.ObjectArrayList; 13 | import it.unimi.dsi.fastutil.objects.ObjectList; 14 | import it.unimi.dsi.fastutil.objects.ObjectListIterator; 15 | + 16 | +import java.util.Map; 17 | import java.util.concurrent.CompletableFuture; 18 | +import java.util.concurrent.ConcurrentHashMap; 19 | import java.util.concurrent.atomic.AtomicBoolean; 20 | import java.util.function.IntSupplier; 21 | import javax.annotation.Nullable; 22 | @@ -114,7 +117,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl 23 | return totalChunks; 24 | } 25 | 26 | - private final Long2IntOpenHashMap chunksBeingWorkedOn = new Long2IntOpenHashMap(); 27 | + private final Map chunksBeingWorkedOn = new ConcurrentHashMap<>(); // ShreddedPaper - thread-safe chunksBeingWorkedOn 28 | 29 | private void queueTaskForSection(final int chunkX, final int chunkY, final int chunkZ, 30 | final Supplier runnable) { // Paper - rewrite chunk system 31 | @@ -157,21 +160,31 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl 32 | } 33 | updateFuture.isTicketAdded = true; 34 | 35 | - final int references = this.chunksBeingWorkedOn.addTo(key, 1); 36 | - if (references == 0) { 37 | - final ChunkPos pos = new ChunkPos(chunkX, chunkZ); 38 | - world.getChunkSource().addRegionTicket(ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos); 39 | - } 40 | - 41 | - updateFuture.onComplete.thenAcceptAsync((final Void ignore) -> { 42 | - final int newReferences = this.chunksBeingWorkedOn.get(key); 43 | - if (newReferences == 1) { 44 | - this.chunksBeingWorkedOn.remove(key); 45 | + // ShreddedPaper start - thread-safe chunksBeingWorkedOn 46 | + this.chunksBeingWorkedOn.compute(key, (k, references) -> { 47 | + if (references == null || references == 0) { 48 | final ChunkPos pos = new ChunkPos(chunkX, chunkZ); 49 | - world.getChunkSource().removeRegionTicket(ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos); 50 | - } else { 51 | - this.chunksBeingWorkedOn.put(key, newReferences - 1); 52 | + world.getChunkSource().addRegionTicket(ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos); 53 | + return 1; 54 | } 55 | + return references + 1; 56 | + }); 57 | + // ShreddedPaper end - thread-safe chunksBeingWorkedOn 58 | + 59 | + updateFuture.onComplete.thenAcceptAsync((final Void ignore) -> { 60 | + // ShreddedPaper start - thread-safe chunksBeingWorkedOn 61 | + this.chunksBeingWorkedOn.compute(key, (k, newReferences) -> { 62 | + if (newReferences == null) { 63 | + throw new NullPointerException("newReferences should not be null here! Should be at least 1 or larger"); 64 | + } 65 | + if (newReferences == 1) { 66 | + final ChunkPos pos = new ChunkPos(chunkX, chunkZ); 67 | + world.getChunkSource().removeRegionTicket(ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos); 68 | + return null; // Removes from chunksBeingWorkedOn 69 | + } 70 | + return newReferences - 1; 71 | + }); 72 | + // ShreddedPaper end - thread-safe chunksBeingWorkedOn 73 | }, world.getChunkSource().chunkMap.mainThreadExecutor).whenComplete((final Void ignore, final Throwable thr) -> { 74 | if (thr != null) { 75 | LOGGER.error("Failed to remove ticket level for post chunk task " + new ChunkPos(chunkX, chunkZ), thr); 76 | -------------------------------------------------------------------------------- /patches/server/0041-kill-command.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Wed, 26 Jun 2024 10:23:46 +0900 4 | Subject: [PATCH] /kill command 5 | 6 | 7 | diff --git a/src/main/java/io/multipaper/shreddedpaper/ShreddedPaper.java b/src/main/java/io/multipaper/shreddedpaper/ShreddedPaper.java 8 | index 0671578ca4a43ff5ba3853440ad8ee96018c9916..478b67cfaef9507713b70d837cdcb675f47c10a7 100644 9 | --- a/src/main/java/io/multipaper/shreddedpaper/ShreddedPaper.java 10 | +++ b/src/main/java/io/multipaper/shreddedpaper/ShreddedPaper.java 11 | @@ -9,6 +9,8 @@ import org.bukkit.Location; 12 | import org.bukkit.craftbukkit.CraftWorld; 13 | import io.multipaper.shreddedpaper.region.RegionPos; 14 | 15 | +import java.util.function.Consumer; 16 | + 17 | public class ShreddedPaper { 18 | 19 | public static void runSync(Location location, Runnable runnable) { 20 | @@ -39,6 +41,14 @@ public class ShreddedPaper { 21 | } 22 | } 23 | 24 | + public static void ensureSync(Entity entity, Consumer consumer) { 25 | + if (!isSync((ServerLevel) entity.level(), entity.chunkPosition())) { 26 | + entity.getBukkitEntity().taskScheduler.schedule(consumer, null, 1); 27 | + } else { 28 | + consumer.accept(entity); 29 | + } 30 | + } 31 | + 32 | public static void ensureSync(ServerLevel serverLevel, BlockPos blockPos, Runnable runnable) { 33 | ensureSync(serverLevel, new ChunkPos(blockPos), runnable); 34 | } 35 | diff --git a/src/main/java/net/minecraft/server/commands/KillCommand.java b/src/main/java/net/minecraft/server/commands/KillCommand.java 36 | index c2974a6bd6851b54d1df2689195d896baf4906ee..610e43fa1b388e34ec50d03f5c87ab3372f64822 100644 37 | --- a/src/main/java/net/minecraft/server/commands/KillCommand.java 38 | +++ b/src/main/java/net/minecraft/server/commands/KillCommand.java 39 | @@ -3,6 +3,9 @@ package net.minecraft.server.commands; 40 | import com.google.common.collect.ImmutableList; 41 | import com.mojang.brigadier.CommandDispatcher; 42 | import java.util.Collection; 43 | +import java.util.function.Consumer; 44 | + 45 | +import io.multipaper.shreddedpaper.ShreddedPaper; 46 | import net.minecraft.commands.CommandSourceStack; 47 | import net.minecraft.commands.Commands; 48 | import net.minecraft.commands.arguments.EntityArgument; 49 | @@ -24,7 +27,7 @@ public class KillCommand { 50 | 51 | private static int kill(CommandSourceStack source, Collection targets) { 52 | for (Entity entity : targets) { 53 | - entity.kill(); 54 | + ShreddedPaper.ensureSync(entity, Entity::kill); // ShreddedPaper - run on right thread 55 | } 56 | 57 | if (targets.size() == 1) { 58 | -------------------------------------------------------------------------------- /patches/server/0042-Purpur-teleportIfOutsideBorder-use-teleportAsync.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Mon, 1 Jul 2024 14:11:36 +0900 4 | Subject: [PATCH] Purpur teleportIfOutsideBorder - use teleportAsync 5 | 6 | 7 | diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java 8 | index 6f840e64c2f416f5e98c9c462fd29e3291a56fcf..d54e696d425e83a286de12ea8082ae7acfe3fb05 100644 9 | --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java 10 | +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java 11 | @@ -440,7 +440,7 @@ public abstract class LivingEntity extends Entity implements Attackable { 12 | double d1 = this.level().getWorldBorder().getDamagePerBlock(); 13 | 14 | if (d1 > 0.0D) { 15 | - if (level().purpurConfig.teleportIfOutsideBorder && this instanceof ServerPlayer serverPlayer) { serverPlayer.teleport(io.papermc.paper.util.MCUtil.toLocation(level(), ((ServerLevel) level()).getSharedSpawnPos())); return; } // Purpur 16 | + if (level().purpurConfig.teleportIfOutsideBorder && this instanceof ServerPlayer serverPlayer) { serverPlayer.getBukkitEntity().teleportAsync(io.papermc.paper.util.MCUtil.toLocation(level(), ((ServerLevel) level()).getSharedSpawnPos())); return; } // Purpur // ShreddedPaper - use teleportAsync 17 | this.hurt(this.damageSources().outOfBorder(), (float) Math.max(1, Mth.floor(-d0 * d1))); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /patches/server/0043-Ensure-worlds-are-loaded-on-the-global-scheduler.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Wed, 3 Jul 2024 19:10:59 +0900 4 | Subject: [PATCH] Ensure worlds are loaded on the global scheduler 5 | 6 | 7 | diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java 8 | index c11924979735e79655a2a0ae5485cd6ef74555b1..edc9a4c2aa5621f520525c9846df09d177f0765f 100644 9 | --- a/src/main/java/net/minecraft/server/MinecraftServer.java 10 | +++ b/src/main/java/net/minecraft/server/MinecraftServer.java 11 | @@ -807,6 +807,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 3 | Date: Sat, 20 Jul 2024 01:23:29 +0900 4 | Subject: [PATCH] Plugin not folia supported warning 5 | 6 | 7 | diff --git a/src/main/java/io/multipaper/shreddedpaper/threading/SynchronousPluginExecution.java b/src/main/java/io/multipaper/shreddedpaper/threading/SynchronousPluginExecution.java 8 | index 3625ee53f17766aae695c0d9e35d755e331e6c56..1740789c109533b952c4407175eb733f7edc0290 100644 9 | --- a/src/main/java/io/multipaper/shreddedpaper/threading/SynchronousPluginExecution.java 10 | +++ b/src/main/java/io/multipaper/shreddedpaper/threading/SynchronousPluginExecution.java 11 | @@ -56,6 +56,8 @@ public class SynchronousPluginExecution { 12 | pluginsToLock = cachedDependencyLists.computeIfAbsent(plugin.getName(), (name) -> { 13 | TreeSet dependencyList = new TreeSet<>(Comparator.naturalOrder()); 14 | fillPluginsToLock(plugin, dependencyList); 15 | + LOGGER.info("Plugin {} does not support Folia! Initializing synchronous execution. This may cause a performance degradation.", plugin.getName()); 16 | + LOGGER.info("Dependency list calculated for {}: {}", plugin.getName(), dependencyList); 17 | return new ArrayList<>(dependencyList); 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /patches/server/0045-Thread-safe-scoreboards.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Sat, 27 Jul 2024 14:11:39 +0900 4 | Subject: [PATCH] Thread-safe scoreboards 5 | 6 | 7 | diff --git a/src/main/java/net/minecraft/server/ServerScoreboard.java b/src/main/java/net/minecraft/server/ServerScoreboard.java 8 | index f180001493146ef0d54079a8b2b47ad7decc24ca..775666dbf839d184bd50b0cb91a79658b298522d 100644 9 | --- a/src/main/java/net/minecraft/server/ServerScoreboard.java 10 | +++ b/src/main/java/net/minecraft/server/ServerScoreboard.java 11 | @@ -7,6 +7,8 @@ import java.util.List; 12 | import java.util.Objects; 13 | import java.util.Optional; 14 | import java.util.Set; 15 | +import java.util.concurrent.ConcurrentHashMap; 16 | +import java.util.concurrent.CopyOnWriteArrayList; 17 | import javax.annotation.Nullable; 18 | import net.minecraft.core.HolderLookup; 19 | import net.minecraft.nbt.CompoundTag; 20 | @@ -31,8 +33,8 @@ import net.minecraft.world.scores.ScoreboardSaveData; 21 | public class ServerScoreboard extends Scoreboard { 22 | 23 | private final MinecraftServer server; 24 | - private final Set trackedObjectives = Sets.newHashSet(); 25 | - private final List dirtyListeners = Lists.newArrayList(); 26 | + private final Set trackedObjectives = ConcurrentHashMap.newKeySet(); // ShreddedPaper 27 | + private final List dirtyListeners = new CopyOnWriteArrayList<>(); // ShreddedPaper - rarely modified, thus CopyOnWriteArrayList suffices 28 | 29 | public ServerScoreboard(MinecraftServer server) { 30 | this.server = server; 31 | diff --git a/src/main/java/net/minecraft/world/scores/PlayerTeam.java b/src/main/java/net/minecraft/world/scores/PlayerTeam.java 32 | index 9464054912e19fc78dd965b71fce20a18564b351..0304ebb48563f4e337855eb091b2c1a8f6cbd590 100644 33 | --- a/src/main/java/net/minecraft/world/scores/PlayerTeam.java 34 | +++ b/src/main/java/net/minecraft/world/scores/PlayerTeam.java 35 | @@ -3,6 +3,7 @@ package net.minecraft.world.scores; 36 | import com.google.common.collect.Sets; 37 | import java.util.Collection; 38 | import java.util.Set; 39 | +import java.util.concurrent.ConcurrentHashMap; 40 | import javax.annotation.Nullable; 41 | import net.minecraft.ChatFormatting; 42 | import net.minecraft.network.chat.CommonComponents; 43 | @@ -17,7 +18,7 @@ public class PlayerTeam extends Team { 44 | private static final int BIT_SEE_INVISIBLES = 1; 45 | private final Scoreboard scoreboard; 46 | private final String name; 47 | - private final Set players = Sets.newHashSet(); 48 | + private final Set players = ConcurrentHashMap.newKeySet(); // ShreddedPaper 49 | private Component displayName; 50 | private Component playerPrefix = CommonComponents.EMPTY; 51 | private Component playerSuffix = CommonComponents.EMPTY; 52 | diff --git a/src/main/java/net/minecraft/world/scores/Scoreboard.java b/src/main/java/net/minecraft/world/scores/Scoreboard.java 53 | index 121b57ec018bffc05904e7596e0963e2bb545dc1..2af1dcad557607800e3742fd2866885481f4835c 100644 54 | --- a/src/main/java/net/minecraft/world/scores/Scoreboard.java 55 | +++ b/src/main/java/net/minecraft/world/scores/Scoreboard.java 56 | @@ -15,6 +15,7 @@ import java.util.EnumMap; 57 | import java.util.List; 58 | import java.util.Map; 59 | import java.util.Objects; 60 | +import java.util.concurrent.ConcurrentHashMap; 61 | import java.util.function.Consumer; 62 | import javax.annotation.Nullable; 63 | import net.minecraft.core.HolderLookup; 64 | @@ -31,12 +32,12 @@ import org.slf4j.Logger; 65 | public class Scoreboard { 66 | public static final String HIDDEN_SCORE_PREFIX = "#"; 67 | private static final Logger LOGGER = LogUtils.getLogger(); 68 | - private final Object2ObjectMap objectivesByName = new Object2ObjectOpenHashMap<>(16, 0.5F); 69 | - private final Reference2ObjectMap> objectivesByCriteria = new Reference2ObjectOpenHashMap<>(); 70 | - private final Map playerScores = new Object2ObjectOpenHashMap<>(16, 0.5F); 71 | + private final Map objectivesByName = new ConcurrentHashMap<>(); // ShreddedPaper 72 | + private final Map> objectivesByCriteria = new ConcurrentHashMap<>(); // ShreddedPaper 73 | + private final Map playerScores = new ConcurrentHashMap<>(); // ShreddedPaper 74 | private final Map displayObjectives = new EnumMap<>(DisplaySlot.class); 75 | - private final Object2ObjectMap teamsByName = new Object2ObjectOpenHashMap<>(); 76 | - private final Object2ObjectMap teamsByPlayer = new Object2ObjectOpenHashMap<>(); 77 | + private final Map teamsByName = new ConcurrentHashMap<>(); // ShreddedPaper 78 | + private final Map teamsByPlayer = new ConcurrentHashMap<>(); // ShreddedPaper 79 | 80 | @Nullable 81 | public Objective getObjective(@Nullable String name) { 82 | @@ -88,7 +89,7 @@ public class Scoreboard { 83 | } 84 | 85 | @Override 86 | - public void set(int score) { 87 | + public void set(int newScoreValue) { // ShreddedPaper compile error - rename to newScoreValue 88 | if (!bl) { 89 | throw new IllegalStateException("Cannot modify read-only score"); 90 | } else { 91 | @@ -101,8 +102,8 @@ public class Scoreboard { 92 | } 93 | } 94 | 95 | - if (score != score.value()) { 96 | - score.value(score); 97 | + if (newScoreValue != score.value()) { // ShreddedPaper compile error - rename to newScoreValue 98 | + score.value(newScoreValue); // ShreddedPaper compile error - rename to newScoreValue 99 | bl = true; 100 | } 101 | 102 | @@ -240,7 +241,7 @@ public class Scoreboard { 103 | this.onObjectiveRemoved(objective); 104 | } 105 | 106 | - public void setDisplayObjective(DisplaySlot slot, @Nullable Objective objective) { 107 | + public synchronized void setDisplayObjective(DisplaySlot slot, @Nullable Objective objective) { // ShreddedPaper 108 | this.displayObjectives.put(slot, objective); 109 | } 110 | 111 | @@ -251,6 +252,7 @@ public class Scoreboard { 112 | 113 | @Nullable 114 | public PlayerTeam getPlayerTeam(String name) { 115 | + if (name == null) return null; // ShreddedPaper 116 | return this.teamsByName.get(name); 117 | } 118 | 119 | -------------------------------------------------------------------------------- /patches/server/0046-Optimization-entity-activation-check-frequency.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Wed, 31 Jul 2024 00:46:51 +0900 4 | Subject: [PATCH] Optimization: entity-activation-check-frequency 5 | 6 | 7 | diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java 8 | index c9a10c0b952f7590a48d0b96cacd8e012543d805..ad566654e2e09a5f47de5583353553263e448ee9 100644 9 | --- a/src/main/java/org/spigotmc/ActivationRange.java 10 | +++ b/src/main/java/org/spigotmc/ActivationRange.java 11 | @@ -1,5 +1,6 @@ 12 | package org.spigotmc; 13 | 14 | +import io.multipaper.shreddedpaper.config.ShreddedPaperConfiguration; 15 | import net.minecraft.core.BlockPos; 16 | import net.minecraft.server.MinecraftServer; 17 | import net.minecraft.server.level.ServerChunkCache; 18 | @@ -199,7 +200,8 @@ public class ActivationRange 19 | 20 | for ( Player player : world.players() ) 21 | { 22 | - player.activatedTick = MinecraftServer.currentTick; 23 | + if (ShreddedPaperConfiguration.get().optimizations.entityActivationCheckFrequency > 1 && (player.getId() + MinecraftServer.currentTick) % ShreddedPaperConfiguration.get().optimizations.entityActivationCheckFrequency != 0) continue; // ShreddedPaper - Configurable entity activation check frequency 24 | + player.activatedTick = MinecraftServer.currentTick + Math.max(0, ShreddedPaperConfiguration.get().optimizations.entityActivationCheckFrequency); // ShreddedPaper - Configurable entity activation check frequency 25 | if ( world.spigotConfig.ignoreSpectatorActivation && player.isSpectator() ) 26 | { 27 | continue; 28 | @@ -260,16 +262,16 @@ public class ActivationRange 29 | */ 30 | private static void activateEntity(Entity entity) 31 | { 32 | - if ( MinecraftServer.currentTick > entity.activatedTick ) 33 | + if ( MinecraftServer.currentTick + Math.max(0, ShreddedPaperConfiguration.get().optimizations.entityActivationCheckFrequency) > entity.activatedTick ) // ShreddedPaper - Configurable entity activation check frequency 34 | { 35 | if ( entity.defaultActivationState ) 36 | { // Pufferfish - diff on change 37 | - entity.activatedTick = MinecraftServer.currentTick; 38 | + entity.activatedTick = MinecraftServer.currentTick + Math.max(0, ShreddedPaperConfiguration.get().optimizations.entityActivationCheckFrequency); // ShreddedPaper - Configurable entity activation check frequency 39 | return; 40 | } 41 | if ( entity.activationType.boundingBox.intersects( entity.getBoundingBox() ) ) 42 | { // Pufferfish - diff on change 43 | - entity.activatedTick = MinecraftServer.currentTick; 44 | + entity.activatedTick = MinecraftServer.currentTick + Math.max(0, ShreddedPaperConfiguration.get().optimizations.entityActivationCheckFrequency); // ShreddedPaper - Configurable entity activation check frequency 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /patches/server/0047-handlingTick.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Sat, 3 Aug 2024 19:37:13 +0900 4 | Subject: [PATCH] handlingTick 5 | 6 | 7 | diff --git a/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChunkTicker.java b/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChunkTicker.java 8 | index 3ebb70ecb854ba0172148e48a07578dc9c90d853..783ec3ddae8829237bfdb3a3b79f8bd83b16db34 100644 9 | --- a/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChunkTicker.java 10 | +++ b/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChunkTicker.java 11 | @@ -83,6 +83,8 @@ public class ShreddedPaperChunkTicker { 12 | 13 | region.tickTasks(); 14 | 15 | + level.handlingTickThreadLocal.set(true); 16 | + 17 | level.blockTicks.tick(region.getRegionPos(), level.getGameTime(), level.paperConfig().environment.maxBlockTicks, level::tickBlock); 18 | level.fluidTicks.tick(region.getRegionPos(), level.getGameTime(), level.paperConfig().environment.maxBlockTicks, level::tickFluid); 19 | 20 | @@ -90,6 +92,8 @@ public class ShreddedPaperChunkTicker { 21 | 22 | level.runBlockEvents(region); 23 | 24 | + level.handlingTickThreadLocal.set(false); 25 | + 26 | region.forEachTickingEntity(ShreddedPaperEntityTicker::tickEntity); 27 | 28 | region.forEachTrackedEntity(ShreddedPaperEntityTicker::processTrackQueue); 29 | diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java 30 | index a7f3e1334e6df8fa9f2fce374be0df6d60bdc6e5..e8128156fb04be56291f09c8b71aeb944a18f2dd 100644 31 | --- a/src/main/java/net/minecraft/server/level/ServerLevel.java 32 | +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java 33 | @@ -209,7 +209,7 @@ public class ServerLevel extends Level implements WorldGenLevel { 34 | protected final Raids raids; 35 | // private final ObjectLinkedOpenHashSet blockEvents; // ShreddedPaper - moved into each region 36 | private final ThreadLocal> blockEventsToRescheduleThreadLocal; // ShreddedPaper 37 | - private boolean handlingTick; 38 | + public ThreadLocal handlingTickThreadLocal = ThreadLocal.withInitial(() -> false); // ShreddedPaper 39 | private final List customSpawners; 40 | @Nullable 41 | private EndDragonFight dragonFight; 42 | @@ -836,7 +836,7 @@ public class ServerLevel extends Level implements WorldGenLevel { 43 | public void tick(BooleanSupplier shouldKeepTicking) { 44 | //ProfilerFiller gameprofilerfiller = this.getProfiler(); // Purpur 45 | 46 | - this.handlingTick = true; 47 | + this.handlingTickThreadLocal.set(true); // ShreddedPaper 48 | TickRateManager tickratemanager = this.tickRateManager(); 49 | boolean flag = tickratemanager.runsNormally(); 50 | 51 | @@ -905,7 +905,7 @@ public class ServerLevel extends Level implements WorldGenLevel { 52 | // this.timings.doSounds.stopTiming(); // Spigot // Purpur 53 | } 54 | 55 | - this.handlingTick = false; 56 | + this.handlingTickThreadLocal.set(false); // ShreddedPaper 57 | //gameprofilerfiller.pop(); // Purpur 58 | boolean flag1 = !paperConfig().unsupportedSettings.disableWorldTickingWhenEmpty || !this.players.isEmpty() || !this.getForcedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players // Paper - restore this 59 | 60 | @@ -1223,7 +1223,7 @@ public class ServerLevel extends Level implements WorldGenLevel { 61 | } 62 | 63 | public boolean isHandlingTick() { 64 | - return this.handlingTick; 65 | + return this.handlingTickThreadLocal.get(); 66 | } 67 | 68 | public boolean canSleepThroughNights() { 69 | -------------------------------------------------------------------------------- /patches/server/0048-Synchronize-playersSentChunkTo.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Sun, 4 Aug 2024 00:46:29 +0900 4 | Subject: [PATCH] Synchronize playersSentChunkTo 5 | 6 | 7 | diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java 8 | index 8da6278f841e0ac032ae74ed75b7689d43e2cdfb..d1d0bc714ddb0ba148c215b69f62d0d195459b37 100644 9 | --- a/src/main/java/net/minecraft/server/level/ChunkHolder.java 10 | +++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java 11 | @@ -102,15 +102,19 @@ public class ChunkHolder { 12 | private final com.destroystokyo.paper.util.maplist.ReferenceList playersSentChunkTo = new com.destroystokyo.paper.util.maplist.ReferenceList<>(); 13 | 14 | public void addPlayer(ServerPlayer player) { 15 | + synchronized (playersSentChunkTo) { // ShreddedPaper 16 | if (!this.playersSentChunkTo.add(player)) { 17 | throw new IllegalStateException("Already sent chunk " + this.pos + " in world '" + this.chunkMap.level.getWorld().getName() + "' to player " + player); 18 | } 19 | + } // ShreddedPaper 20 | } 21 | 22 | public void removePlayer(ServerPlayer player) { 23 | + synchronized (playersSentChunkTo) { // ShreddedPaper 24 | if (!this.playersSentChunkTo.remove(player)) { 25 | throw new IllegalStateException("Have not sent chunk " + this.pos + " in world '" + this.chunkMap.level.getWorld().getName() + "' to player " + player); 26 | } 27 | + } // ShreddedPaper 28 | } 29 | 30 | public boolean hasChunkBeenSent() { 31 | @@ -118,7 +122,9 @@ public class ChunkHolder { 32 | } 33 | 34 | public boolean hasBeenSent(ServerPlayer to) { 35 | + synchronized (playersSentChunkTo) { // ShreddedPaper 36 | return this.playersSentChunkTo.contains(to); 37 | + } // ShreddedPaper 38 | } 39 | // Paper end - replace player chunk loader 40 | public ChunkHolder(ChunkPos pos, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.PlayerProvider playersWatchingChunkProvider, io.papermc.paper.chunk.system.scheduling.NewChunkHolder newChunkHolder) { // Paper - rewrite chunk system 41 | @@ -360,6 +366,7 @@ public class ChunkHolder { 42 | public List getPlayers(boolean onlyOnWatchDistanceEdge) { 43 | List ret = new java.util.ArrayList<>(); 44 | 45 | + synchronized (playersSentChunkTo) { // ShreddedPaper 46 | for (int i = 0, len = this.playersSentChunkTo.size(); i < len; ++i) { 47 | ServerPlayer player = this.playersSentChunkTo.getUnchecked(i); 48 | if (onlyOnWatchDistanceEdge && !this.chunkMap.level.playerChunkLoader.isChunkSent(player, this.pos.x, this.pos.z, onlyOnWatchDistanceEdge)) { 49 | @@ -367,6 +374,7 @@ public class ChunkHolder { 50 | } 51 | ret.add(player); 52 | } 53 | + } // ShreddedPaper 54 | 55 | return ret; 56 | } 57 | -------------------------------------------------------------------------------- /patches/server/0049-PotentialCalculator.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Tue, 6 Aug 2024 22:08:49 +0900 4 | Subject: [PATCH] PotentialCalculator 5 | 6 | 7 | diff --git a/src/main/java/net/minecraft/world/level/PotentialCalculator.java b/src/main/java/net/minecraft/world/level/PotentialCalculator.java 8 | index d18eedff64d1dbf5257798c5d41da7ef76d19e3e..c4e80757fda67ec763545fcfde6be6299ba4d63f 100644 9 | --- a/src/main/java/net/minecraft/world/level/PotentialCalculator.java 10 | +++ b/src/main/java/net/minecraft/world/level/PotentialCalculator.java 11 | @@ -9,7 +9,7 @@ public class PotentialCalculator { 12 | 13 | public void addCharge(BlockPos pos, double mass) { 14 | if (mass != 0.0) { 15 | - this.charges.add(new PotentialCalculator.PointCharge(pos, mass)); 16 | + this.charges.add(new PotentialCalculator.PointCharge(pos, mass)); // ShreddedPaper - doesn't need a lock, if a value isn't added it's not a big deal 17 | } 18 | } 19 | 20 | @@ -19,7 +19,8 @@ public class PotentialCalculator { 21 | } else { 22 | double d = 0.0; 23 | 24 | - for (PotentialCalculator.PointCharge pointCharge : this.charges) { 25 | + for (int i = 0; i < this.charges.size(); i++) { // ShreddedPaper - prevent CME - list is only ever added to 26 | + PotentialCalculator.PointCharge pointCharge = this.charges.get(i); // ShreddedPaper - prevent CME - list is only ever added to 27 | d += pointCharge.getPotentialChange(pos); 28 | } 29 | 30 | -------------------------------------------------------------------------------- /patches/server/0050-Remove-firework-rocket-if-entity-teleported-away.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Wed, 7 Aug 2024 13:48:10 +0900 4 | Subject: [PATCH] Remove firework rocket if entity teleported away 5 | 6 | 7 | diff --git a/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java b/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java 8 | index 6671796d292fbc922a94271136f5a7a4bbdedaca..4671f34ba2796c1284af5bd9b2d2edfe37869ad6 100644 9 | --- a/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java 10 | +++ b/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java 11 | @@ -4,6 +4,8 @@ import java.util.Iterator; 12 | import java.util.List; 13 | import java.util.OptionalInt; 14 | import javax.annotation.Nullable; 15 | + 16 | +import io.papermc.paper.util.TickThread; 17 | import net.minecraft.core.BlockPos; 18 | import net.minecraft.core.component.DataComponents; 19 | import net.minecraft.core.particles.ParticleTypes; 20 | @@ -11,6 +13,7 @@ import net.minecraft.nbt.CompoundTag; 21 | import net.minecraft.network.syncher.EntityDataAccessor; 22 | import net.minecraft.network.syncher.EntityDataSerializers; 23 | import net.minecraft.network.syncher.SynchedEntityData; 24 | +import net.minecraft.server.level.ServerLevel; 25 | import net.minecraft.sounds.SoundEvents; 26 | import net.minecraft.sounds.SoundSource; 27 | import net.minecraft.world.entity.Entity; 28 | @@ -148,6 +151,12 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier { 29 | vec3d = Vec3.ZERO; 30 | } 31 | 32 | + // ShreddedPaper start - remove firework rocket if entity teleported away 33 | + if (!TickThread.isTickThreadFor((ServerLevel) this.level(), new Vec3(this.attachedToEntity.getX() + vec3d.x, this.attachedToEntity.getY() + vec3d.y, this.attachedToEntity.getZ() + vec3d.z))) { 34 | + this.discard(); 35 | + return; 36 | + } 37 | + // ShreddedPaper end - remove firework rocket if entity teleported away 38 | this.setPos(this.attachedToEntity.getX() + vec3d.x, this.attachedToEntity.getY() + vec3d.y, this.attachedToEntity.getZ() + vec3d.z); 39 | this.setDeltaMovement(this.attachedToEntity.getDeltaMovement()); 40 | } 41 | -------------------------------------------------------------------------------- /patches/server/0051-Bukkit-API-thread-checks.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Sat, 10 Aug 2024 21:21:57 +0900 4 | Subject: [PATCH] Bukkit API thread checks 5 | 6 | 7 | diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java 8 | index 92f1ea81b5e90529905d9c508aca18c31443ff6a..b3ebd6a29895777166174b40006177eaa5d751ee 100644 9 | --- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java 10 | +++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java 11 | @@ -9,6 +9,9 @@ import java.util.Objects; 12 | import java.util.concurrent.locks.LockSupport; 13 | import java.util.function.BooleanSupplier; 14 | import java.util.function.Predicate; 15 | + 16 | +import io.multipaper.shreddedpaper.threading.ShreddedPaperTickThread; 17 | +import io.papermc.paper.util.TickThread; 18 | import net.minecraft.core.BlockPos; 19 | import net.minecraft.core.Holder; 20 | import net.minecraft.core.Registry; 21 | @@ -82,6 +85,7 @@ public class CraftChunk implements Chunk { 22 | } 23 | 24 | public ChunkAccess getHandle(ChunkStatus chunkStatus) { 25 | + if (ShreddedPaperTickThread.isShreddedPaperTickThread() && !TickThread.isTickThreadFor(this.worldServer, this.x, this.z)) TickThread.failedTickThreadCheck("Cannot get chunk from a region that is not ours!", "world=" + this.worldServer.convertable.getLevelId() + ", chunkpos=[" + this.x + "," + this.z + "]"); // ShreddedPaper - regions 26 | ChunkAccess chunkAccess = this.worldServer.getChunk(this.x, this.z, chunkStatus); 27 | 28 | // SPIGOT-7332: Get unwrapped extension 29 | diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java 30 | index 3723dd028800c81f3f392657f1b209884864c5ba..12091e6b2696f7adf5275988ecc4cdc51e6760be 100644 31 | --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java 32 | +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java 33 | @@ -7,6 +7,7 @@ import com.google.common.collect.ImmutableMap; 34 | import com.mojang.datafixers.util.Pair; 35 | import io.multipaper.shreddedpaper.ShreddedPaper; 36 | import io.multipaper.shreddedpaper.region.RegionPos; 37 | +import io.multipaper.shreddedpaper.threading.ShreddedPaperTickThread; 38 | import io.multipaper.shreddedpaper.threading.SynchronousPluginExecution; 39 | import io.papermc.paper.util.TickThread; 40 | import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; 41 | @@ -355,6 +356,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { 42 | 43 | @Override 44 | public Chunk getChunkAt(int x, int z) { 45 | + if (ShreddedPaperTickThread.isShreddedPaperTickThread() && !TickThread.isTickThreadFor(this.world, x, z)) TickThread.failedTickThreadCheck("Cannot get chunk from a region that is not ours!", "world=" + this.world.convertable.getLevelId() + ", chunkpos=[" + x + "," + z + "]"); // ShreddedPaper - regions 46 | warnUnsafeChunk("getting a faraway chunk", x, z); // Paper 47 | // Paper start - add ticket to hold chunk for a little while longer if plugin accesses it 48 | net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); 49 | diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java 50 | index ed30feb916daecc1d9b9aaa854ac5b832aa59757..59f9d77239c03a56eb2dfad4599f32ec77894830 100644 51 | --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java 52 | +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java 53 | @@ -6,6 +6,8 @@ import java.util.Collection; 54 | import java.util.Collections; 55 | import java.util.List; 56 | import java.util.stream.Collectors; 57 | + 58 | +import io.papermc.paper.util.TickThread; 59 | import net.minecraft.core.BlockPos; 60 | import net.minecraft.core.Direction; 61 | import net.minecraft.server.level.ServerLevel; 62 | @@ -75,6 +77,7 @@ public class CraftBlock implements Block { 63 | } 64 | 65 | public net.minecraft.world.level.block.state.BlockState getNMS() { 66 | + if (this.world instanceof ServerLevel serverLevel) TickThread.ensureTickThread(serverLevel, this.position, "Cannot get block asynchronously!"); 67 | return this.world.getBlockState(this.position); 68 | } 69 | 70 | @@ -162,6 +165,7 @@ public class CraftBlock implements Block { 71 | 72 | @Override 73 | public byte getData() { 74 | + if (this.world instanceof ServerLevel serverLevel) TickThread.ensureTickThread(serverLevel, this.position, "Cannot get block asynchronously!"); 75 | net.minecraft.world.level.block.state.BlockState blockData = this.world.getBlockState(this.position); 76 | return CraftMagicNumbers.toLegacyData(blockData); 77 | } 78 | @@ -194,6 +198,7 @@ public class CraftBlock implements Block { 79 | } 80 | 81 | boolean setTypeAndData(final net.minecraft.world.level.block.state.BlockState blockData, final boolean applyPhysics) { 82 | + if (this.world instanceof ServerLevel serverLevel) TickThread.ensureTickThread(serverLevel, this.position, "Cannot set block asynchronously!"); 83 | return CraftBlock.setTypeAndData(this.world, this.position, this.getNMS(), blockData, applyPhysics); 84 | } 85 | 86 | @@ -226,6 +231,7 @@ public class CraftBlock implements Block { 87 | 88 | @Override 89 | public Material getType() { 90 | + if (this.world instanceof ServerLevel serverLevel) TickThread.ensureTickThread(serverLevel, this.position, "Cannot get block asynchronously!"); 91 | return this.world.getBlockState(this.position).getBukkitMaterial(); // Paper - optimise getType calls 92 | } 93 | 94 | -------------------------------------------------------------------------------- /patches/server/0052-give-command.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Sun, 11 Aug 2024 18:32:56 +0900 4 | Subject: [PATCH] /give command 5 | 6 | 7 | diff --git a/src/main/java/net/minecraft/server/commands/GiveCommand.java b/src/main/java/net/minecraft/server/commands/GiveCommand.java 8 | index 9f1c8a62bda242781a0966fa2fc01534261423c7..0f50a67a1d3023043725cdc3d8d6e554154d9171 100644 9 | --- a/src/main/java/net/minecraft/server/commands/GiveCommand.java 10 | +++ b/src/main/java/net/minecraft/server/commands/GiveCommand.java 11 | @@ -7,6 +7,8 @@ import com.mojang.brigadier.builder.RequiredArgumentBuilder; 12 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 13 | import java.util.Collection; 14 | import java.util.Iterator; 15 | + 16 | +import io.multipaper.shreddedpaper.ShreddedPaper; 17 | import net.minecraft.commands.CommandBuildContext; 18 | import net.minecraft.commands.CommandSourceStack; 19 | import net.minecraft.commands.arguments.EntityArgument; 20 | @@ -89,10 +91,11 @@ public class GiveCommand { 21 | 22 | l -= i1; 23 | ItemStack itemstack1 = item.createItemStack(i1, false); 24 | + ShreddedPaper.ensureSync(entityplayer, () -> { // ShreddedPaper - run on player's thread 25 | boolean flag = entityplayer.getInventory().add(itemstack1); 26 | ItemEntity entityitem; 27 | 28 | - if (org.purpurmc.purpur.PurpurConfig.disableGiveCommandDrops) continue; // Purpur - add config option for toggling give command dropping 29 | + if (org.purpurmc.purpur.PurpurConfig.disableGiveCommandDrops) return; // Purpur - add config option for toggling give command dropping // ShreddedPaper - run on player's thread 30 | if (flag && itemstack1.isEmpty()) { 31 | entityitem = entityplayer.drop(itemstack, false, false, false); // CraftBukkit - SPIGOT-2942: Add boolean to call event 32 | if (entityitem != null) { 33 | @@ -108,6 +111,7 @@ public class GiveCommand { 34 | entityitem.setTarget(entityplayer.getUUID()); 35 | } 36 | } 37 | + }); // ShreddedPaper - run on player's thread 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /patches/server/0053-clear-command.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Sun, 11 Aug 2024 18:50:42 +0900 4 | Subject: [PATCH] /clear command 5 | 6 | 7 | diff --git a/src/main/java/net/minecraft/server/commands/ClearInventoryCommands.java b/src/main/java/net/minecraft/server/commands/ClearInventoryCommands.java 8 | index 4e6171ca870649114d4c7460baad2982173da09e..f3efba187cfb35e08458de59a171c82bff8d3280 100644 9 | --- a/src/main/java/net/minecraft/server/commands/ClearInventoryCommands.java 10 | +++ b/src/main/java/net/minecraft/server/commands/ClearInventoryCommands.java 11 | @@ -6,7 +6,10 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; 12 | import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; 13 | import java.util.Collection; 14 | import java.util.Collections; 15 | +import java.util.concurrent.CompletableFuture; 16 | import java.util.function.Predicate; 17 | + 18 | +import io.multipaper.shreddedpaper.ShreddedPaper; 19 | import net.minecraft.commands.CommandBuildContext; 20 | import net.minecraft.commands.CommandSourceStack; 21 | import net.minecraft.commands.Commands; 22 | @@ -65,9 +68,16 @@ public class ClearInventoryCommands { 23 | int i = 0; 24 | 25 | for (ServerPlayer serverPlayer : targets) { 26 | - i += serverPlayer.getInventory().clearOrCountMatchingItems(item, maxCount, serverPlayer.inventoryMenu.getCraftSlots()); 27 | - serverPlayer.containerMenu.broadcastChanges(); 28 | - serverPlayer.inventoryMenu.slotsChanged(serverPlayer.getInventory()); 29 | + // ShreddedPaper start - run on correct thread 30 | + CompletableFuture future = new CompletableFuture<>(); 31 | + ShreddedPaper.ensureSync(serverPlayer, () -> { 32 | + int j = serverPlayer.getInventory().clearOrCountMatchingItems(item, maxCount, serverPlayer.inventoryMenu.getCraftSlots()); 33 | + serverPlayer.containerMenu.broadcastChanges(); 34 | + serverPlayer.inventoryMenu.slotsChanged(serverPlayer.getInventory()); 35 | + future.complete(j); 36 | + }); 37 | + i += future.getNow(1); // ShreddedPaper - default to 1 if player in on a different thread 38 | + // ShreddedPaper end - run on correct thread 39 | } 40 | 41 | if (i == 0) { 42 | -------------------------------------------------------------------------------- /patches/server/0054-Send-ping-packet-later.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Wed, 14 Aug 2024 01:13:18 +0900 4 | Subject: [PATCH] Send ping packet later 5 | 6 | 7 | diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java 8 | index edc9a4c2aa5621f520525c9846df09d177f0765f..7f725ba3071c4be51f075569f8a0d66756b0d1d8 100644 9 | --- a/src/main/java/net/minecraft/server/MinecraftServer.java 10 | +++ b/src/main/java/net/minecraft/server/MinecraftServer.java 11 | @@ -1887,6 +1887,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0 && !this.chatSpamTickCount.compareAndSet(spam, spam - 1); ) ; 44 | if (tabSpamLimiter.get() > 0) tabSpamLimiter.getAndDecrement(); // Paper - configurable tab spam limits 45 | -------------------------------------------------------------------------------- /patches/server/0055-Moving-into-another-region.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Fri, 23 Aug 2024 01:03:30 +0900 4 | Subject: [PATCH] Moving into another region 5 | 6 | 7 | diff --git a/src/main/java/io/multipaper/shreddedpaper/region/RegionPos.java b/src/main/java/io/multipaper/shreddedpaper/region/RegionPos.java 8 | index c8caf9240e1aa5e434a47cbdf4b7bf68e4025586..c5fc229de6f086a0fe66d05380b612d2468d6079 100644 9 | --- a/src/main/java/io/multipaper/shreddedpaper/region/RegionPos.java 10 | +++ b/src/main/java/io/multipaper/shreddedpaper/region/RegionPos.java 11 | @@ -16,6 +16,7 @@ public class RegionPos { 12 | public static final int REGION_SIZE; // eg 8 (for an 8x8 region) 13 | public static final int REGION_SHIFT; // eg 3 (1 << 3 == 8) 14 | public static final int REGION_SIZE_MASK; // eg 7 (9 % 8 == 9 & 7 == 1) 15 | + public static final int MAX_DISTANCE_SQR; 16 | 17 | static { 18 | // desiredRegionSize = 7 -> shift = 3, size = 8, mask = 7 19 | @@ -43,6 +44,8 @@ public class RegionPos { 20 | } 21 | 22 | LOGGER.info("Using region size: {}, shift={}, mask={}", REGION_SIZE, REGION_SHIFT, REGION_SIZE_MASK); 23 | + 24 | + MAX_DISTANCE_SQR = RegionPos.REGION_SIZE * 16 * RegionPos.REGION_SIZE * 16; 25 | } 26 | 27 | public final int x; 28 | diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java 29 | index 6b2c2e2f782630a3b7e25532260cc61e02c8049f..f45ae96bdb13e8d697fc48a50f3e405a3b681a73 100644 30 | --- a/src/main/java/net/minecraft/world/entity/Entity.java 31 | +++ b/src/main/java/net/minecraft/world/entity/Entity.java 32 | @@ -1176,7 +1176,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess 33 | public void move(MoverType movementType, Vec3 movement) { 34 | final Vec3 originalMovement = movement; // Paper - Expose pre-collision velocity 35 | // Paper start - detailed watchdog information 36 | - io.papermc.paper.util.TickThread.ensureTickThread("Cannot move an entity off-main"); 37 | + io.papermc.paper.util.TickThread.ensureTickThread(this, "Cannot move an entity off-main"); 38 | + BlockPos newPos = new BlockPos((int) (this.getX() + movement.x), (int) (this.getY() + movement.y), (int) (this.getZ() + movement.z)); 39 | + if (!TickThread.isTickThreadFor((ServerLevel) this.level, newPos)) { 40 | + LOGGER.warn("Trying to move {} from {} to {}!!! movement={} getDeltaMovement={}", this, this.blockPosition(), newPos, movement, this.getDeltaMovement()); 41 | + } 42 | synchronized (this.posLock) { 43 | this.moveStartX = this.getX(); 44 | this.moveStartY = this.getY(); 45 | @@ -2271,7 +2275,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess 46 | } 47 | delta = event.getKnockback(); 48 | } 49 | - this.setDeltaMovement(this.getDeltaMovement().add(delta.getX(), delta.getY(), delta.getZ())); 50 | + // ShreddedPaper start - limit push velocity 51 | + Vec3 newDelta = this.getDeltaMovement().add(delta.getX(), delta.getY(), delta.getZ()); 52 | + if (newDelta.lengthSqr() > RegionPos.MAX_DISTANCE_SQR) { 53 | + newDelta = newDelta.normalize().scale(RegionPos.REGION_SIZE * 16); 54 | + } 55 | + this.setDeltaMovement(newDelta); 56 | + // ShreddedPaper end - limit push velocity 57 | // Paper end - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent 58 | this.hasImpulse = true; 59 | } 60 | @@ -4780,6 +4790,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess 61 | } 62 | 63 | public void setDeltaMovement(Vec3 velocity) { 64 | + // ShreddedPaper start - why is something setting the entity velocity to larger than one region... 65 | + if (velocity.horizontalDistanceSqr() > RegionPos.MAX_DISTANCE_SQR + 1 && velocity.horizontalDistanceSqr() > this.deltaMovement.horizontalDistanceSqr()) { 66 | + LOGGER.warn("Velocity is being set larger than the ShreddedPaper region size: {} for entity {}", velocity, this, new Exception("Velocity larger than region size")); 67 | + } 68 | + // ShreddedPaper end - why is something setting the entity velocity to larger than one region... 69 | synchronized (this.posLock) { // Paper 70 | this.deltaMovement = velocity; 71 | } // Paper 72 | diff --git a/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java b/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java 73 | index fca3786d0a3f99a3e61e7a4b2251361276eff9d7..74f0577397c8665c9bea3f79775dc26c15543e62 100644 74 | --- a/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java 75 | +++ b/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java 76 | @@ -1,5 +1,6 @@ 77 | package net.minecraft.world.entity.projectile; 78 | 79 | +import io.multipaper.shreddedpaper.region.RegionPos; 80 | import net.minecraft.core.BlockPos; 81 | import net.minecraft.core.particles.ParticleTypes; 82 | import net.minecraft.nbt.CompoundTag; 83 | @@ -137,6 +138,8 @@ public class EyeOfEnder extends Entity implements ItemSupplier { 84 | 85 | int i = this.getY() < this.ty ? 1 : -1; 86 | 87 | + if (d6 > RegionPos.MAX_DISTANCE_SQR) d6 = RegionPos.MAX_DISTANCE_SQR; // ShreddedPaper - keep within a region 88 | + 89 | vec3d = new Vec3(Math.cos((double) f1) * d6, d7 + ((double) i - d7) * 0.014999999664723873D, Math.sin((double) f1) * d6); 90 | this.setDeltaMovement(vec3d); 91 | } 92 | -------------------------------------------------------------------------------- /patches/server/0056-Don-t-allow-actions-outside-of-our-region.patch: -------------------------------------------------------------------------------- 1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 2 | From: PureGero 3 | Date: Wed, 28 Aug 2024 13:47:10 +0900 4 | Subject: [PATCH] Don't allow actions outside of our region 5 | 6 | 7 | diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java 8 | index 510653da484f93666158553ffdc1200976481322..eccd1b92bddf6322ad3b95d4ba00934fe873ac20 100644 9 | --- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java 10 | +++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java 11 | @@ -6,6 +6,7 @@ import javax.annotation.Nullable; 12 | 13 | import io.multipaper.shreddedpaper.region.RegionPos; 14 | import io.multipaper.shreddedpaper.threading.ShreddedPaperChunkTicker; 15 | +import io.papermc.paper.util.TickThread; 16 | import net.minecraft.advancements.CriteriaTriggers; 17 | import net.minecraft.core.BlockPos; 18 | import net.minecraft.core.Direction; 19 | @@ -135,7 +136,7 @@ public class ServerPlayerGameMode { 20 | BlockState iblockdata; 21 | 22 | if (this.hasDelayedDestroy) { 23 | - iblockdata = this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks 24 | + iblockdata = !TickThread.isTickThreadFor(this.level, this.delayedDestroyPos) ? null : this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks // ShreddedPaper - Don't allow digging into chunks outside our region 25 | if (iblockdata == null || iblockdata.isAir()) { // Paper - Don't allow digging into unloaded chunks 26 | this.hasDelayedDestroy = false; 27 | } else { 28 | @@ -148,7 +149,7 @@ public class ServerPlayerGameMode { 29 | } 30 | } else if (this.isDestroyingBlock) { 31 | // Paper start - Don't allow digging into unloaded chunks; don't want to do same logic as above, return instead 32 | - iblockdata = this.level.getBlockStateIfLoaded(this.destroyPos); 33 | + iblockdata = !TickThread.isTickThreadFor(this.level, this.destroyPos) ? null : this.level.getBlockStateIfLoaded(this.destroyPos); // ShreddedPaper - Don't allow digging into chunks outside our region 34 | if (iblockdata == null) { 35 | this.isDestroyingBlock = false; 36 | return; 37 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.util.Locale 2 | 3 | pluginManagement { 4 | repositories { 5 | gradlePluginPortal() 6 | maven("https://papermc.io/repo/repository/maven-public/") 7 | // io.github.goooler.shadow 8 | maven("https://plugins.gradle.org/m2/") 9 | } 10 | } 11 | 12 | rootProject.name = "shreddedpaper" 13 | 14 | for (name in listOf("ShreddedPaper-API", "ShreddedPaper-Server")) { 15 | val projName = name.toLowerCase(Locale.ENGLISH) 16 | include(projName) 17 | findProject(":$projName")!!.projectDir = file(name) 18 | } 19 | --------------------------------------------------------------------------------