├── .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 | 
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 | 
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ShreddedPaper
2 |
3 | [](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 | [](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 extends Entity> run, Consumer extends Entity> 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 extends Entity> 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 |
--------------------------------------------------------------------------------