├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── .gitmodules
├── LICENSE
├── README.md
├── TECHNICAL_DETAILS.md
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
└── main
├── java
└── ca
│ └── spottedleaf
│ └── starlight
│ ├── common
│ ├── ScalableLuxEntrypoint.java
│ ├── blockstate
│ │ └── ExtendedAbstractBlockState.java
│ ├── chunk
│ │ └── ExtendedChunk.java
│ ├── config
│ │ └── Config.java
│ ├── light
│ │ ├── BlockStarLightEngine.java
│ │ ├── SWMRNibbleArray.java
│ │ ├── SkyStarLightEngine.java
│ │ ├── StarLightEngine.java
│ │ ├── StarLightInterface.java
│ │ └── StarLightLightingProvider.java
│ ├── thread
│ │ ├── GlobalExecutors.java
│ │ ├── LockTokenImpl.java
│ │ ├── SchedulingUtil.java
│ │ └── SimpleTask.java
│ ├── util
│ │ ├── CoordinateUtils.java
│ │ ├── IntegerUtil.java
│ │ ├── SaveUtil.java
│ │ └── WorldUtil.java
│ └── world
│ │ ├── ExtendedSerializableChunkData.java
│ │ └── ExtendedWorld.java
│ └── mixin
│ ├── client
│ ├── multiplayer
│ │ └── ClientPacketListenerMixin.java
│ └── world
│ │ └── ClientLevelMixin.java
│ └── common
│ ├── blockstate
│ └── BlockStateBaseMixin.java
│ ├── chunk
│ ├── ChunkAccessMixin.java
│ ├── EmptyLevelChunkMixin.java
│ ├── ImposterProtoChunkMixin.java
│ ├── LevelChunkMixin.java
│ └── ProtoChunkMixin.java
│ ├── lightengine
│ ├── LevelLightEngineMixin.java
│ └── ThreadedLevelLightEngineMixin.java
│ └── world
│ ├── LevelMixin.java
│ ├── SerializableChunkDataMixin.java
│ ├── ServerWorldMixin.java
│ └── WorldGenRegionMixin.java
└── resources
├── assets
└── scalablelux
│ └── icon.png
├── fabric.mod.json
├── scalablelux.accesswidener
└── scalablelux.mixins.json
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | # Automatically build the project and run any configured tests for every push
2 | # and submitted pull request. This can help catch issues that only occur on
3 | # certain platforms or Java versions, and provides a first line of defence
4 | # against bad commits.
5 |
6 | name: build
7 | on: [pull_request, push]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-22.04
12 | steps:
13 | - name: checkout repository
14 | uses: actions/checkout@v4
15 | with:
16 | fetch-depth: 0
17 | submodules: true
18 | - name: validate gradle wrapper
19 | uses: gradle/actions/wrapper-validation@v3
20 | - name: setup jdk 21
21 | uses: actions/setup-java@v4
22 | with:
23 | java-version: 21
24 | distribution: 'microsoft'
25 | - name: build
26 | run: ./gradlew build
27 | - name: capture build artifacts
28 | uses: actions/upload-artifact@v4
29 | with:
30 | name: Artifacts
31 | path: build/libs/
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # User-specific stuff
2 | .idea/
3 |
4 | *.iml
5 | *.ipr
6 | *.iws
7 |
8 | # IntelliJ
9 | out/
10 | # mpeltonen/sbt-idea plugin
11 | .idea_modules/
12 |
13 | # JIRA plugin
14 | atlassian-ide-plugin.xml
15 |
16 | # Compiled class file
17 | *.class
18 |
19 | # Log file
20 | *.log
21 |
22 | # BlueJ files
23 | *.ctxt
24 |
25 | # Package Files #
26 | *.jar
27 | *.war
28 | *.nar
29 | *.ear
30 | *.zip
31 | *.tar.gz
32 | *.rar
33 |
34 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
35 | hs_err_pid*
36 |
37 | *~
38 |
39 | # temporary files which can be created if a process still has a handle open of a deleted file
40 | .fuse_hidden*
41 |
42 | # KDE directory preferences
43 | .directory
44 |
45 | # Linux trash folder which might appear on any partition or disk
46 | .Trash-*
47 |
48 | # .nfs files are created when an open file is removed but is still being accessed
49 | .nfs*
50 |
51 | # General
52 | .DS_Store
53 | .AppleDouble
54 | .LSOverride
55 |
56 | # Icon must end with two \r
57 | Icon
58 |
59 | # Thumbnails
60 | ._*
61 |
62 | # Files that might appear in the root of a volume
63 | .DocumentRevisions-V100
64 | .fseventsd
65 | .Spotlight-V100
66 | .TemporaryItems
67 | .Trashes
68 | .VolumeIcon.icns
69 | .com.apple.timemachine.donotpresent
70 |
71 | # Directories potentially created on remote AFP share
72 | .AppleDB
73 | .AppleDesktop
74 | Network Trash Folder
75 | Temporary Items
76 | .apdisk
77 |
78 | # Windows thumbnail cache files
79 | Thumbs.db
80 | Thumbs.db:encryptable
81 | ehthumbs.db
82 | ehthumbs_vista.db
83 |
84 | # Dump file
85 | *.stackdump
86 |
87 | # Folder config file
88 | [Dd]esktop.ini
89 |
90 | # Recycle Bin used on file shares
91 | $RECYCLE.BIN/
92 |
93 | # Windows Installer files
94 | *.cab
95 | *.msi
96 | *.msix
97 | *.msm
98 | *.msp
99 |
100 | # Windows shortcuts
101 | *.lnk
102 |
103 | .gradle
104 | build/
105 |
106 | # Ignore Gradle GUI config
107 | gradle-app.setting
108 |
109 | # Cache of project
110 | .gradletasknamecache
111 |
112 | **/build/
113 |
114 | # Common working directory
115 | run/
116 |
117 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
118 | !gradle-wrapper.jar
119 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "FlowSched"]
2 | path = FlowSched
3 | url = https://github.com/RelativityMC/FlowSched.git
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ScalableLux
2 | A Fabric mod based on Starlight that improves the performance of light updates in Minecraft.
3 |
4 | ## Why does this fork exist?
5 | - Starlight is no longer maintained as a mod [since Mar 8, 2024](https://github.com/PaperMC/Starlight/commit/cca03d62da48e876ac79196bad16864e8a96bbeb).
6 | - The performance of vanilla lighting engine is still a bottleneck for high-performance chunk generation.
7 | - The base Starlight is still [100% faster than vanilla](),
8 | allowing the chunk system to scale beyond 24 threads.
9 | - Starlight's "stateless" design allows for parallel light updates, further widening the performance gap.
10 | It is still [rather important for dedicated servers with more players to stress chunk generation](https://gist.github.com/Spottedleaf/6cc1acdd03a9b7ac34699bf5e8f1b85c#is-starlight-obsolete).
11 | Therefore, it is still important for Fabric or other modded servers with plenty of players.
12 |
13 | ## What does this fork do?
14 | - Contains all the performance improvements from Starlight with additional bug fixes.
15 | - Optionally allows for parallel light updates, bringing significant performance improvement in high-speed
16 | world generation and heavy light updates scenarios.
17 |
18 | ## Building and setting up
19 |
20 | #### Initial setup
21 | Run the following commands in the root directory:
22 |
23 | ```
24 | git submodule update --init
25 | ./build.sh up
26 | ./build.sh patch
27 | ```
28 |
29 | #### Creating a patch
30 | See [CONTRIBUTING.md](CONTRIBUTING.md) for more detailed information.
31 |
32 |
33 | #### Compiling
34 | Use the command `./build.sh build`. Compiled jars will be placed under `Starlight-Patched/build/libs/`.
35 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'fabric-loom' version '1.7-SNAPSHOT'
3 | id 'maven-publish'
4 | id 'com.github.johnrengelman.shadow' version '8.1.1'
5 | }
6 |
7 | configurations {
8 | shadowInclude
9 | }
10 |
11 | loom.runs.all {
12 | // https://github.com/SpongePowered/Mixin/wiki/Mixin-Java-System-Properties
13 | vmArg("-Dmixin.debug=true")
14 | }
15 |
16 | /*
17 | * Gets the version name from the latest Git tag
18 | */
19 | // https://stackoverflow.com/questions/28498688/gradle-script-to-autoversion-and-include-the-commit-hash-in-android
20 | def getGitCommit = { ->
21 | def stdout = new ByteArrayOutputStream()
22 | exec {
23 | commandLine 'git', 'rev-parse', '--short', 'HEAD'
24 | standardOutput = stdout
25 | // workingDir = ".."
26 | }
27 | return stdout.toString().trim()
28 | }
29 |
30 | archivesBaseName = project.archives_base_name
31 | version = project.mod_version + "+fabric." + getGitCommit()
32 | group = project.maven_group
33 |
34 | dependencies {
35 | //to change the versions see the gradle.properties file
36 | minecraft "com.mojang:minecraft:${project.minecraft_version}"
37 | //mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
38 | mappings loom.officialMojangMappings()
39 | modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
40 |
41 | // Fabric API. This is technically optional, but you probably want it anyway.
42 | //modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
43 |
44 | // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs.
45 | // You may need to force-disable transitiveness on them.
46 |
47 | shadowInclude("com.ishland.flowsched:flowsched") {
48 | transitive false
49 | }
50 | implementation("com.ishland.flowsched:flowsched")
51 | }
52 |
53 | processResources {
54 | inputs.property "version", project.version
55 |
56 | filesMatching("fabric.mod.json") {
57 | expand "version": project.version, "minecraft_version": minecraft_version, "loader_version": loader_version, "mod_version": mod_version
58 | }
59 | }
60 |
61 | // ensure that the encoding is set to UTF-8, no matter what the system default is
62 | // this fixes some edge cases with special characters not displaying correctly
63 | // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
64 | tasks.withType(JavaCompile) {
65 | options.encoding = "UTF-8"
66 | }
67 |
68 | loom {
69 | accessWidenerPath = file("src/main/resources/scalablelux.accesswidener")
70 | }
71 |
72 | // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
73 | // if it is present.
74 | // If you remove this task, sources will not be generated.
75 | java {
76 | // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
77 | // if it is present.
78 | // If you remove this line, sources will not be generated.
79 | withSourcesJar()
80 |
81 | sourceCompatibility = JavaVersion.VERSION_17
82 | targetCompatibility = JavaVersion.VERSION_17
83 | }
84 | jar {
85 | from "LICENSE"
86 | }
87 |
88 | shadowJar {
89 | archiveClassifier = "all-dev"
90 | configurations = [ project.configurations.shadowInclude ]
91 |
92 | relocate "com.ishland.flowsched", "ca.spottedleaf.starlight.interndep.flowsched"
93 | }
94 |
95 | remapJar {
96 | input = shadowJar.archiveFile
97 | archiveFileName = shadowJar.archiveFileName.get().replaceAll("-dev\\.jar\$", ".jar")
98 | addNestedDependencies = true
99 | dependsOn shadowJar
100 | }
101 |
102 | clean.dependsOn gradle.includedBuild('FlowSched').task(':clean')
103 |
104 | // make build reproducible
105 | tasks.withType(AbstractArchiveTask) {
106 | preserveFileTimestamps = false
107 | reproducibleFileOrder = true
108 | }
109 |
110 | // configure the maven publication
111 | publishing {
112 | publications {
113 | mavenJava(MavenPublication) {
114 | // add all the jars that should be included when publishing to maven
115 | artifact(remapJar) {
116 | builtBy remapJar
117 | }
118 | artifact(sourcesJar) {
119 | builtBy remapSourcesJar
120 | }
121 | }
122 | }
123 |
124 | // select the repositories you want to publish to
125 | repositories {
126 | // uncomment to publish to the local maven
127 | // mavenLocal()
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Done to increase the memory available to gradle.
2 | org.gradle.jvmargs=-Xmx2G
3 | org.gradle.daemon=false
4 | # Fabric Properties
5 | # check these on https://modmuss50.me/fabric.html
6 | minecraft_version=1.21.4
7 | loader_version=0.16.9
8 | # Mod Properties
9 | mod_version=0.1.2+beta.1
10 | maven_group=ca.spottedleaf.starlight
11 | archives_base_name=ScalableLux
12 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RelativityMC/ScalableLux/c6ec02104ec2104fbb3c99f9782641aa3b5c6515/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.10.2-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/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/master/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 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
84 |
85 | APP_NAME="Gradle"
86 | APP_BASE_NAME=${0##*/}
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 | MAX_FD=$( ulimit -H -n ) ||
147 | warn "Could not query maximum file descriptor limit"
148 | esac
149 | case $MAX_FD in #(
150 | '' | soft) :;; #(
151 | *)
152 | ulimit -n "$MAX_FD" ||
153 | warn "Could not set maximum file descriptor limit to $MAX_FD"
154 | esac
155 | fi
156 |
157 | # Collect all arguments for the java command, stacking in reverse order:
158 | # * args from the command line
159 | # * the main class name
160 | # * -classpath
161 | # * -D...appname settings
162 | # * --module-path (only if needed)
163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
164 |
165 | # For Cygwin or MSYS, switch paths to Windows format before running java
166 | if "$cygwin" || "$msys" ; then
167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
169 |
170 | JAVACMD=$( cygpath --unix "$JAVACMD" )
171 |
172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
173 | for arg do
174 | if
175 | case $arg in #(
176 | -*) false ;; # don't mess with options #(
177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
178 | [ -e "$t" ] ;; #(
179 | *) false ;;
180 | esac
181 | then
182 | arg=$( cygpath --path --ignore --mixed "$arg" )
183 | fi
184 | # Roll the args list around exactly as many times as the number of
185 | # args, so each arg winds up back in the position where it started, but
186 | # possibly modified.
187 | #
188 | # NB: a `for` loop captures its iteration list before it begins, so
189 | # changing the positional parameters here affects neither the number of
190 | # iterations, nor the values presented in `arg`.
191 | shift # remove old arg
192 | set -- "$@" "$arg" # push replacement arg
193 | done
194 | fi
195 |
196 | # Collect all arguments for the java command;
197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
198 | # shell script including quotes and variable substitutions, so put them in
199 | # double quotes to make sure that they get re-expanded; and
200 | # * put everything else in single quotes, so that it's not re-expanded.
201 |
202 | set -- \
203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
204 | -classpath "$CLASSPATH" \
205 | org.gradle.wrapper.GradleWrapperMain \
206 | "$@"
207 |
208 | # Stop when "xargs" is not available.
209 | if ! command -v xargs >/dev/null 2>&1
210 | then
211 | die "xargs is not available"
212 | fi
213 |
214 | # Use "xargs" to parse quoted args.
215 | #
216 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
217 | #
218 | # In Bash we could simply go:
219 | #
220 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
221 | # set -- "${ARGS[@]}" "$@"
222 | #
223 | # but POSIX shell has neither arrays nor command substitution, so instead we
224 | # post-process each arg (as a line of input to sed) to backslash-escape any
225 | # character that might be a shell metacharacter, then use eval to reverse
226 | # that process (while maintaining the separation between arguments), and wrap
227 | # the whole thing up as a single "set" statement.
228 | #
229 | # This will of course break if any of these variables contains a newline or
230 | # an unmatched quote.
231 | #
232 |
233 | eval "set -- $(
234 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
235 | xargs -n1 |
236 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
237 | tr '\n' ' '
238 | )" '"$@"'
239 |
240 | exec "$JAVACMD" "$@"
241 |
--------------------------------------------------------------------------------
/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 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if %ERRORLEVEL% equ 0 goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if %ERRORLEVEL% equ 0 goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | set EXIT_CODE=%ERRORLEVEL%
84 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
85 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
86 | exit /b %EXIT_CODE%
87 |
88 | :mainEnd
89 | if "%OS%"=="Windows_NT" endlocal
90 |
91 | :omega
92 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | maven {
4 | name = 'Fabric'
5 | url = 'https://maven.fabricmc.net/'
6 | }
7 | mavenCentral()
8 | gradlePluginPortal()
9 | }
10 | }
11 |
12 | rootProject.name = "ScalableLux"
13 |
14 | includeBuild('FlowSched') {
15 | dependencySubstitution {
16 | substitute module('com.ishland.flowsched:flowsched') using project(':')
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/common/ScalableLuxEntrypoint.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.common;
2 |
3 | import ca.spottedleaf.starlight.common.config.Config;
4 | import net.fabricmc.api.ModInitializer;
5 |
6 | public class ScalableLuxEntrypoint implements ModInitializer {
7 | @Override
8 | public void onInitialize() {
9 | Config.init();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/common/blockstate/ExtendedAbstractBlockState.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.common.blockstate;
2 |
3 | public interface ExtendedAbstractBlockState {
4 |
5 | public boolean isConditionallyFullOpaque();
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/common/chunk/ExtendedChunk.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.common.chunk;
2 |
3 | import ca.spottedleaf.starlight.common.light.SWMRNibbleArray;
4 |
5 | public interface ExtendedChunk {
6 |
7 | public SWMRNibbleArray[] getBlockNibbles();
8 | public void setBlockNibbles(final SWMRNibbleArray[] nibbles);
9 |
10 | public SWMRNibbleArray[] getSkyNibbles();
11 | public void setSkyNibbles(final SWMRNibbleArray[] nibbles);
12 |
13 | public boolean[] getSkyEmptinessMap();
14 | public void setSkyEmptinessMap(final boolean[] emptinessMap);
15 |
16 | public boolean[] getBlockEmptinessMap();
17 | public void setBlockEmptinessMap(final boolean[] emptinessMap);
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/common/config/Config.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.common.config;
2 |
3 | import ca.spottedleaf.starlight.common.thread.SchedulingUtil;
4 | import net.fabricmc.loader.api.FabricLoader;
5 | import net.fabricmc.loader.api.ModContainer;
6 | import net.fabricmc.loader.api.metadata.CustomValue;
7 | import org.apache.logging.log4j.LogManager;
8 | import org.apache.logging.log4j.Logger;
9 |
10 | import java.io.IOException;
11 | import java.io.InputStream;
12 | import java.io.OutputStream;
13 | import java.nio.file.Files;
14 | import java.nio.file.Path;
15 | import java.nio.file.StandardOpenOption;
16 | import java.util.Properties;
17 |
18 | public class Config {
19 | private static final Logger LOGGER = LogManager.getLogger();
20 |
21 | public static final int PARALLELISM;
22 | public static final boolean USE_STARLIGHT_FORMAT;
23 |
24 | static {
25 | final Properties properties = new Properties();
26 | final Properties newProperties = new Properties();
27 | final Path path = FabricLoader.getInstance().getConfigDir().resolve("scalablelux.properties");
28 | if (Files.isRegularFile(path)) {
29 | try (InputStream in = Files.newInputStream(path, StandardOpenOption.CREATE)) {
30 | properties.load(in);
31 | } catch (IOException e) {
32 | throw new RuntimeException(e);
33 | }
34 | }
35 |
36 | if (!SchedulingUtil.isExternallyManaged()) {
37 | int parallelism = getInt(properties, newProperties, "parallelism", -1);
38 | if (parallelism < 1) {
39 | parallelism = Math.max(1, Runtime.getRuntime().availableProcessors() / 3);
40 | }
41 | PARALLELISM = parallelism;
42 | } else {
43 | PARALLELISM = Math.max(1, Runtime.getRuntime().availableProcessors() / 3);
44 | }
45 | USE_STARLIGHT_FORMAT = getBoolean(properties, newProperties, "use_starlight_format", false);
46 |
47 | if (!newProperties.isEmpty()) {
48 | try (OutputStream out = Files.newOutputStream(path, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
49 | newProperties.store(out, "Configuration file for ScalableLux");
50 | } catch (IOException e) {
51 | throw new RuntimeException(e);
52 | }
53 | }
54 | }
55 |
56 | public static void init() {
57 | }
58 |
59 | private static int getInt(Properties properties, Properties newProperties, String key, int def) {
60 | try {
61 | final int i = Integer.parseInt(properties.getProperty(key));
62 | newProperties.setProperty(key, String.valueOf(i));
63 | return i;
64 | } catch (NumberFormatException e) {
65 | newProperties.setProperty(key, String.valueOf(def));
66 | return def;
67 | }
68 | }
69 |
70 | private static boolean getBoolean(Properties properties, Properties newProperties, String key, boolean def) {
71 | boolean boolean0 = getBoolean0(properties, newProperties, key, def);
72 | for (ModContainer modContainer : FabricLoader.getInstance().getAllMods()) {
73 | final CustomValue incompatibilitiesValue = modContainer.getMetadata().getCustomValue("scalablelux:incompatibleConfig");
74 | if (incompatibilitiesValue != null && incompatibilitiesValue.getType() == CustomValue.CvType.ARRAY) {
75 | final CustomValue.CvArray incompatibilities = incompatibilitiesValue.getAsArray();
76 | for (CustomValue value : incompatibilities) {
77 | if (value.getType() == CustomValue.CvType.STRING && value.getAsString().equals(key)) {
78 | final String message;
79 | if (Boolean.getBoolean("scalablelux.ignoreIncompatibleConfig")) {
80 | message = String.format("Ignoring incompatibility of %s (defined in %s@%s)",
81 | key, modContainer.getMetadata().getId(), modContainer.getMetadata().getVersion().getFriendlyString());
82 | } else {
83 | message = String.format("Forcing %s in scalablelux.properties to be disabled (defined in %s@%s)",
84 | key, modContainer.getMetadata().getId(), modContainer.getMetadata().getVersion().getFriendlyString());
85 | boolean0 = false;
86 | }
87 | LOGGER.warn(message);
88 | }
89 | }
90 | }
91 | }
92 | return boolean0;
93 | }
94 |
95 | private static boolean getBoolean0(Properties properties, Properties newProperties, String key, boolean def) {
96 | try {
97 | final boolean b = parseBoolean(properties.getProperty(key));
98 | newProperties.setProperty(key, String.valueOf(b));
99 | return b;
100 | } catch (NumberFormatException e) {
101 | newProperties.setProperty(key, String.valueOf(def));
102 | return def;
103 | }
104 | }
105 |
106 | private static boolean parseBoolean(String string) {
107 | if (string == null) throw new NumberFormatException("null");
108 | if (string.trim().equalsIgnoreCase("true")) return true;
109 | if (string.trim().equalsIgnoreCase("false")) return false;
110 | throw new NumberFormatException(string);
111 | }
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.common.light;
2 |
3 | import ca.spottedleaf.starlight.common.blockstate.ExtendedAbstractBlockState;
4 | import ca.spottedleaf.starlight.common.chunk.ExtendedChunk;
5 | import net.minecraft.core.BlockPos;
6 | import net.minecraft.world.level.Level;
7 | import net.minecraft.world.level.block.state.BlockState;
8 | import net.minecraft.world.level.chunk.ChunkAccess;
9 | import net.minecraft.world.level.chunk.status.ChunkStatus;
10 | import net.minecraft.world.level.chunk.ImposterProtoChunk;
11 | import net.minecraft.world.level.chunk.LevelChunk;
12 | import net.minecraft.world.level.chunk.LevelChunkSection;
13 | import net.minecraft.world.level.chunk.LightChunkGetter;
14 | import net.minecraft.world.level.chunk.PalettedContainer;
15 | import net.minecraft.world.phys.shapes.Shapes;
16 | import net.minecraft.world.phys.shapes.VoxelShape;
17 | import java.util.ArrayList;
18 | import java.util.Iterator;
19 | import java.util.List;
20 | import java.util.Set;
21 | import java.util.stream.Collectors;
22 |
23 | public final class BlockStarLightEngine extends StarLightEngine {
24 |
25 | public BlockStarLightEngine(final Level world) {
26 | super(false, world);
27 | }
28 |
29 | @Override
30 | protected boolean[] getEmptinessMap(final ChunkAccess chunk) {
31 | return ((ExtendedChunk)chunk).getBlockEmptinessMap();
32 | }
33 |
34 | @Override
35 | protected void setEmptinessMap(final ChunkAccess chunk, final boolean[] to) {
36 | ((ExtendedChunk)chunk).setBlockEmptinessMap(to);
37 | }
38 |
39 | @Override
40 | protected SWMRNibbleArray[] getNibblesOnChunk(final ChunkAccess chunk) {
41 | return ((ExtendedChunk)chunk).getBlockNibbles();
42 | }
43 |
44 | @Override
45 | protected void setNibbles(final ChunkAccess chunk, final SWMRNibbleArray[] to) {
46 | ((ExtendedChunk)chunk).setBlockNibbles(to);
47 | }
48 |
49 | @Override
50 | protected boolean canUseChunk(final ChunkAccess chunk) {
51 | return chunk.getPersistedStatus().isOrAfter(ChunkStatus.LIGHT) && (this.isClientSide || chunk.isLightCorrect());
52 | }
53 |
54 | @Override
55 | protected void setNibbleNull(final int chunkX, final int chunkY, final int chunkZ) {
56 | final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ);
57 | if (nibble != null) {
58 | // de-initialisation is not as straightforward as with sky data, since deinit of block light is typically
59 | // because a block was removed - which can decrease light. with sky data, block breaking can only result
60 | // in increases, and thus the existing sky block check will actually correctly propagate light through
61 | // a null section. so in order to propagate decreases correctly, we can do a couple of things: not remove
62 | // the data section, or do edge checks on ALL axis (x, y, z). however I do not want edge checks running
63 | // for clients at all, as they are expensive. so we don't remove the section, but to maintain the appearence
64 | // of vanilla data management we "hide" them.
65 | nibble.setHidden();
66 | }
67 | }
68 |
69 | @Override
70 | protected void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles) {
71 | if (chunkY < this.minLightSection || chunkY > this.maxLightSection || this.getChunkInCache(chunkX, chunkZ) == null) {
72 | return;
73 | }
74 |
75 | final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ);
76 | if (nibble == null) {
77 | if (!initRemovedNibbles) {
78 | throw new IllegalStateException();
79 | } else {
80 | this.setNibbleInCache(chunkX, chunkY, chunkZ, new SWMRNibbleArray());
81 | }
82 | } else {
83 | nibble.setNonNull();
84 | }
85 | }
86 |
87 | @Override
88 | protected final void checkBlock(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ) {
89 | // blocks can change opacity
90 | // blocks can change emitted light
91 | // blocks can change direction of propagation
92 |
93 | final int encodeOffset = this.coordinateOffset;
94 | final int emittedMask = this.emittedLightMask;
95 |
96 | final int currentLevel = this.getLightLevel(worldX, worldY, worldZ);
97 | final BlockState blockState = this.getBlockState(worldX, worldY, worldZ);
98 | final int emittedLevel = blockState.getLightEmission() & emittedMask;
99 |
100 | this.setLightLevel(worldX, worldY, worldZ, emittedLevel);
101 | // this accounts for change in emitted light that would cause an increase
102 | if (emittedLevel != 0) {
103 | this.appendToIncreaseQueue(
104 | ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1))
105 | | (emittedLevel & 0xFL) << (6 + 6 + 16)
106 | | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4))
107 | | (((ExtendedAbstractBlockState)blockState).isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0)
108 | );
109 | }
110 | // this also accounts for a change in emitted light that would cause a decrease
111 | // this also accounts for the change of direction of propagation (i.e old block was full transparent, new block is full opaque or vice versa)
112 | // as it checks all neighbours (even if current level is 0)
113 | this.appendToDecreaseQueue(
114 | ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1))
115 | | (currentLevel & 0xFL) << (6 + 6 + 16)
116 | | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4))
117 | // always keep sided transparent false here, new block might be conditionally transparent which would
118 | // prevent us from decreasing sources in the directions where the new block is opaque
119 | // if it turns out we were wrong to de-propagate the source, the re-propagate logic WILL always
120 | // catch that and fix it.
121 | );
122 | // re-propagating neighbours (done by the decrease queue) will also account for opacity changes in this block
123 | }
124 |
125 | protected final BlockPos.MutableBlockPos recalcCenterPos = new BlockPos.MutableBlockPos();
126 | protected final BlockPos.MutableBlockPos recalcNeighbourPos = new BlockPos.MutableBlockPos();
127 |
128 | @Override
129 | protected int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ,
130 | final int expect) {
131 | final BlockState centerState = this.getBlockState(worldX, worldY, worldZ);
132 | int level = centerState.getLightEmission() & 0xF;
133 |
134 | if (level >= (15 - 1) || level > expect) {
135 | return level;
136 | }
137 |
138 | final int sectionOffset = this.chunkSectionIndexOffset;
139 | final BlockState conditionallyOpaqueState;
140 | int opacity = Math.max(1, centerState.getLightBlock());
141 | if (opacity >= 15) {
142 | return level; // TODO in older starlight, opacity >= 15 can go into the loop below, but it probably shouldn't
143 | }
144 | if (((ExtendedAbstractBlockState)centerState).isConditionallyFullOpaque()) {
145 | conditionallyOpaqueState = centerState;
146 | } else {
147 | conditionallyOpaqueState = null;
148 | }
149 |
150 | for (final AxisDirection direction : AXIS_DIRECTIONS) {
151 | final int offX = worldX + direction.x;
152 | final int offY = worldY + direction.y;
153 | final int offZ = worldZ + direction.z;
154 |
155 | final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset;
156 |
157 | final int neighbourLevel = this.getLightLevel(sectionIndex, (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8));
158 |
159 | if ((neighbourLevel - 1) <= level) {
160 | // don't need to test transparency, we know it wont affect the result.
161 | continue;
162 | }
163 |
164 | final BlockState neighbourState = this.getBlockState(offX, offY, offZ);
165 | if (((ExtendedAbstractBlockState)neighbourState).isConditionallyFullOpaque()) {
166 | // here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that
167 | // we don't read the blockstate because most of the time this is false, so using the faster
168 | // known transparency lookup results in a net win
169 | this.recalcNeighbourPos.set(offX, offY, offZ);
170 | final VoxelShape neighbourFace = neighbourState.getFaceOcclusionShape(direction.opposite.nms);
171 | final VoxelShape thisFace = conditionallyOpaqueState == null ? Shapes.empty() : conditionallyOpaqueState.getFaceOcclusionShape(direction.nms);
172 | if (Shapes.faceShapeOccludes(thisFace, neighbourFace)) {
173 | // not allowed to propagate
174 | continue;
175 | }
176 | }
177 |
178 | // passed transparency,
179 |
180 | final int calculated = neighbourLevel - opacity;
181 | level = Math.max(calculated, level);
182 | if (level > expect) {
183 | return level;
184 | }
185 | }
186 |
187 | return level;
188 | }
189 |
190 | @Override
191 | protected void propagateBlockChanges(final LightChunkGetter lightAccess, final ChunkAccess atChunk, final Set positions) {
192 | for (final BlockPos pos : positions) {
193 | this.checkBlock(lightAccess, pos.getX(), pos.getY(), pos.getZ());
194 | }
195 |
196 | this.performLightDecrease(lightAccess);
197 | }
198 |
199 | protected List getSources(final LightChunkGetter lightAccess, final ChunkAccess chunk) {
200 | final List sources = new ArrayList<>();
201 |
202 | final int offX = chunk.getPos().x << 4;
203 | final int offZ = chunk.getPos().z << 4;
204 |
205 | final LevelChunkSection[] sections = chunk.getSections();
206 | for (int sectionY = this.minSection; sectionY <= this.maxSection; ++sectionY) {
207 | final LevelChunkSection section = sections[sectionY - this.minSection];
208 | if (section == null || section.hasOnlyAir()) {
209 | // no sources in empty sections
210 | continue;
211 | }
212 | if (!section.maybeHas((final BlockState state) -> {
213 | return state.getLightEmission() > 0;
214 | })) {
215 | // no light sources in palette
216 | continue;
217 | }
218 | final PalettedContainer states = section.states;
219 | final int offY = sectionY << 4;
220 |
221 | for (int index = 0; index < (16 * 16 * 16); ++index) {
222 | final BlockState state = states.get(index);
223 | if (state.getLightEmission() <= 0) {
224 | continue;
225 | }
226 |
227 | // index = x | (z << 4) | (y << 8)
228 | sources.add(new BlockPos(offX | (index & 15), offY | (index >>> 8), offZ | ((index >>> 4) & 15)));
229 | }
230 | }
231 |
232 | return sources;
233 | }
234 |
235 | @Override
236 | public void lightChunk(final LightChunkGetter lightAccess, final ChunkAccess chunk, final boolean needsEdgeChecks) {
237 | // setup sources
238 | final int emittedMask = this.emittedLightMask;
239 | final List positions = this.getSources(lightAccess, chunk);
240 | for (int i = 0, len = positions.size(); i < len; ++i) {
241 | final BlockPos pos = positions.get(i);
242 | final BlockState blockState = this.getBlockState(pos.getX(), pos.getY(), pos.getZ());
243 | final int emittedLight = blockState.getLightEmission() & emittedMask;
244 |
245 | if (emittedLight <= this.getLightLevel(pos.getX(), pos.getY(), pos.getZ())) {
246 | // some other source is brighter
247 | continue;
248 | }
249 |
250 | this.appendToIncreaseQueue(
251 | ((pos.getX() + (pos.getZ() << 6) + (pos.getY() << (6 + 6)) + this.coordinateOffset) & ((1L << (6 + 6 + 16)) - 1))
252 | | (emittedLight & 0xFL) << (6 + 6 + 16)
253 | | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4))
254 | | (((ExtendedAbstractBlockState)blockState).isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0)
255 | );
256 |
257 |
258 | // propagation wont set this for us
259 | this.setLightLevel(pos.getX(), pos.getY(), pos.getZ(), emittedLight);
260 | }
261 |
262 | if (needsEdgeChecks) {
263 | // not required to propagate here, but this will reduce the hit of the edge checks
264 | this.performLightIncrease(lightAccess);
265 |
266 | // verify neighbour edges
267 | this.checkChunkEdges(lightAccess, chunk, this.minLightSection, this.maxLightSection);
268 | } else {
269 | this.propagateNeighbourLevels(lightAccess, chunk, this.minLightSection, this.maxLightSection);
270 |
271 | this.performLightIncrease(lightAccess);
272 | }
273 | }
274 | }
275 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.common.light;
2 |
3 | import net.minecraft.world.level.chunk.DataLayer;
4 | import java.util.ArrayDeque;
5 | import java.util.Arrays;
6 |
7 | // SWMR -> Single Writer Multi Reader Nibble Array
8 | public final class SWMRNibbleArray {
9 |
10 | /*
11 | * Null nibble - nibble does not exist, and should not be written to. Just like vanilla - null
12 | * nibbles are always 0 - and they are never written to directly. Only initialised/uninitialised
13 | * nibbles can be written to.
14 | *
15 | * Uninitialised nibble - They are all 0, but the backing array isn't initialised.
16 | *
17 | * Initialised nibble - Has light data.
18 | */
19 |
20 | protected static final int INIT_STATE_NULL = 0; // null
21 | protected static final int INIT_STATE_UNINIT = 1; // uninitialised
22 | protected static final int INIT_STATE_INIT = 2; // initialised
23 | protected static final int INIT_STATE_HIDDEN = 3; // initialised, but conversion to Vanilla data should be treated as if NULL
24 |
25 | public static final int ARRAY_SIZE = 16 * 16 * 16 / (8/4); // blocks / bytes per block
26 | // this allows us to maintain only 1 byte array when we're not updating
27 | static final ThreadLocal> WORKING_BYTES_POOL = ThreadLocal.withInitial(ArrayDeque::new);
28 |
29 | private static byte[] allocateBytes() {
30 | final byte[] inPool = WORKING_BYTES_POOL.get().pollFirst();
31 | if (inPool != null) {
32 | return inPool;
33 | }
34 |
35 | return new byte[ARRAY_SIZE];
36 | }
37 |
38 | private static void freeBytes(final byte[] bytes) {
39 | WORKING_BYTES_POOL.get().addFirst(bytes);
40 | }
41 |
42 | public static SWMRNibbleArray fromVanilla(final DataLayer nibble) {
43 | if (nibble == null) {
44 | return new SWMRNibbleArray(null, true);
45 | } else if (nibble.isEmpty()) {
46 | return new SWMRNibbleArray();
47 | } else {
48 | return new SWMRNibbleArray(nibble.getData().clone()); // make sure we don't write to the parameter later
49 | }
50 | }
51 |
52 | protected int stateUpdating;
53 | protected volatile int stateVisible;
54 |
55 | protected byte[] storageUpdating;
56 | protected boolean updatingDirty; // only returns whether storageUpdating is dirty
57 | protected volatile byte[] storageVisible;
58 |
59 | public SWMRNibbleArray() {
60 | this(null, false); // lazy init
61 | }
62 |
63 | public SWMRNibbleArray(final byte[] bytes) {
64 | this(bytes, false);
65 | }
66 |
67 | public SWMRNibbleArray(final byte[] bytes, final boolean isNullNibble) {
68 | if (bytes != null && bytes.length != ARRAY_SIZE) {
69 | throw new IllegalArgumentException("Data of wrong length: " + bytes.length);
70 | }
71 | this.stateVisible = this.stateUpdating = bytes == null ? (isNullNibble ? INIT_STATE_NULL : INIT_STATE_UNINIT) : INIT_STATE_INIT;
72 | this.storageUpdating = this.storageVisible = bytes;
73 | }
74 |
75 | public SWMRNibbleArray(final byte[] bytes, final int state) {
76 | if (bytes != null && bytes.length != ARRAY_SIZE) {
77 | throw new IllegalArgumentException("Data of wrong length: " + bytes.length);
78 | }
79 | if (bytes == null && (state == INIT_STATE_INIT || state == INIT_STATE_HIDDEN)) {
80 | throw new IllegalArgumentException("Data cannot be null and have state be initialised");
81 | }
82 | this.stateUpdating = this.stateVisible = state;
83 | this.storageUpdating = this.storageVisible = bytes;
84 | }
85 |
86 | @Override
87 | public String toString() {
88 | StringBuilder stringBuilder = new StringBuilder();
89 | stringBuilder.append("State: ");
90 | switch (this.stateVisible) {
91 | case INIT_STATE_NULL:
92 | stringBuilder.append("null");
93 | break;
94 | case INIT_STATE_UNINIT:
95 | stringBuilder.append("uninitialised");
96 | break;
97 | case INIT_STATE_INIT:
98 | stringBuilder.append("initialised");
99 | break;
100 | case INIT_STATE_HIDDEN:
101 | stringBuilder.append("hidden");
102 | break;
103 | default:
104 | stringBuilder.append("unknown");
105 | break;
106 | }
107 | stringBuilder.append("\nData:\n");
108 |
109 | final byte[] data = this.storageVisible;
110 | if (data != null) {
111 | for (int i = 0; i < 4096; ++i) {
112 | // Copied from NibbleArray#toString
113 | final int level = ((data[i >>> 1] >>> ((i & 1) << 2)) & 0xF);
114 |
115 | stringBuilder.append(Integer.toHexString(level));
116 | if ((i & 15) == 15) {
117 | stringBuilder.append("\n");
118 | }
119 |
120 | if ((i & 255) == 255) {
121 | stringBuilder.append("\n");
122 | }
123 | }
124 | } else {
125 | stringBuilder.append("null");
126 | }
127 |
128 | return stringBuilder.toString();
129 | }
130 |
131 | public SaveState getSaveState() {
132 | synchronized (this) {
133 | final int state = this.stateVisible;
134 | final byte[] data = this.storageVisible;
135 | if (state == INIT_STATE_NULL) {
136 | return null;
137 | }
138 | if (state == INIT_STATE_UNINIT) {
139 | return new SaveState(null, state);
140 | }
141 | final boolean zero = isAllZero(data);
142 | if (zero) {
143 | return state == INIT_STATE_INIT ? new SaveState(null, INIT_STATE_UNINIT) : null;
144 | } else {
145 | return new SaveState(data.clone(), state);
146 | }
147 | }
148 | }
149 |
150 | protected static boolean isAllZero(final byte[] data) {
151 | for (int i = 0; i < (ARRAY_SIZE >>> 4); ++i) {
152 | byte whole = data[i << 4];
153 |
154 | for (int k = 1; k < (1 << 4); ++k) {
155 | whole |= data[(i << 4) | k];
156 | }
157 |
158 | if (whole != 0) {
159 | return false;
160 | }
161 | }
162 |
163 | return true;
164 | }
165 |
166 | // operation type: updating on src, updating on other
167 | public void extrudeLower(final SWMRNibbleArray other) {
168 | if (other.stateUpdating == INIT_STATE_NULL) {
169 | throw new IllegalArgumentException();
170 | }
171 |
172 | if (other.storageUpdating == null) {
173 | this.setUninitialised();
174 | return;
175 | }
176 |
177 | final byte[] src = other.storageUpdating;
178 | final byte[] into;
179 |
180 | if (!this.updatingDirty) {
181 | if (this.storageUpdating != null) {
182 | into = this.storageUpdating = allocateBytes();
183 | } else {
184 | this.storageUpdating = into = allocateBytes();
185 | this.stateUpdating = INIT_STATE_INIT;
186 | }
187 | this.updatingDirty = true;
188 | } else {
189 | into = this.storageUpdating;
190 | }
191 |
192 | final int start = 0;
193 | final int end = (15 | (15 << 4)) >>> 1;
194 |
195 | /* x | (z << 4) | (y << 8) */
196 | for (int y = 0; y <= 15; ++y) {
197 | System.arraycopy(src, start, into, y << (8 - 1), end - start + 1);
198 | }
199 | }
200 |
201 | // operation type: updating
202 | public void setFull() {
203 | if (this.stateUpdating != INIT_STATE_HIDDEN) {
204 | this.stateUpdating = INIT_STATE_INIT;
205 | }
206 | Arrays.fill(this.storageUpdating == null || !this.updatingDirty ? this.storageUpdating = allocateBytes() : this.storageUpdating, (byte)-1);
207 | this.updatingDirty = true;
208 | }
209 |
210 | // operation type: updating
211 | public void setZero() {
212 | if (this.stateUpdating != INIT_STATE_HIDDEN) {
213 | this.stateUpdating = INIT_STATE_INIT;
214 | }
215 | Arrays.fill(this.storageUpdating == null || !this.updatingDirty ? this.storageUpdating = allocateBytes() : this.storageUpdating, (byte)0);
216 | this.updatingDirty = true;
217 | }
218 |
219 | // operation type: updating
220 | public void setNonNull() {
221 | if (this.stateUpdating == INIT_STATE_HIDDEN) {
222 | this.stateUpdating = INIT_STATE_INIT;
223 | return;
224 | }
225 | if (this.stateUpdating != INIT_STATE_NULL) {
226 | return;
227 | }
228 | this.stateUpdating = INIT_STATE_UNINIT;
229 | }
230 |
231 | // operation type: updating
232 | public void setNull() {
233 | this.stateUpdating = INIT_STATE_NULL;
234 | if (this.updatingDirty && this.storageUpdating != null) {
235 | freeBytes(this.storageUpdating);
236 | }
237 | this.storageUpdating = null;
238 | this.updatingDirty = false;
239 | }
240 |
241 | // operation type: updating
242 | public void setUninitialised() {
243 | this.stateUpdating = INIT_STATE_UNINIT;
244 | if (this.storageUpdating != null && this.updatingDirty) {
245 | freeBytes(this.storageUpdating);
246 | }
247 | this.storageUpdating = null;
248 | this.updatingDirty = false;
249 | }
250 |
251 | // operation type: updating
252 | public void setHidden() {
253 | if (this.stateUpdating == INIT_STATE_HIDDEN) {
254 | return;
255 | }
256 | if (this.stateUpdating != INIT_STATE_INIT) {
257 | this.setNull();
258 | } else {
259 | this.stateUpdating = INIT_STATE_HIDDEN;
260 | }
261 | }
262 |
263 | // operation type: updating
264 | public boolean isDirty() {
265 | return this.stateUpdating != this.stateVisible || this.updatingDirty;
266 | }
267 |
268 | // operation type: updating
269 | public boolean isNullNibbleUpdating() {
270 | return this.stateUpdating == INIT_STATE_NULL;
271 | }
272 |
273 | // operation type: visible
274 | public boolean isNullNibbleVisible() {
275 | return this.stateVisible == INIT_STATE_NULL;
276 | }
277 |
278 | // opeartion type: updating
279 | public boolean isUninitialisedUpdating() {
280 | return this.stateUpdating == INIT_STATE_UNINIT;
281 | }
282 |
283 | // operation type: visible
284 | public boolean isUninitialisedVisible() {
285 | return this.stateVisible == INIT_STATE_UNINIT;
286 | }
287 |
288 | // operation type: updating
289 | public boolean isInitialisedUpdating() {
290 | return this.stateUpdating == INIT_STATE_INIT;
291 | }
292 |
293 | // operation type: visible
294 | public boolean isInitialisedVisible() {
295 | return this.stateVisible == INIT_STATE_INIT;
296 | }
297 |
298 | // operation type: updating
299 | public boolean isHiddenUpdating() {
300 | return this.stateUpdating == INIT_STATE_HIDDEN;
301 | }
302 |
303 | // operation type: updating
304 | public boolean isHiddenVisible() {
305 | return this.stateVisible == INIT_STATE_HIDDEN;
306 | }
307 |
308 | // operation type: updating
309 | protected void swapUpdatingAndMarkDirty() {
310 | if (this.updatingDirty) {
311 | return;
312 | }
313 |
314 | if (this.storageUpdating == null) {
315 | this.storageUpdating = allocateBytes();
316 | Arrays.fill(this.storageUpdating, (byte)0);
317 | } else {
318 | System.arraycopy(this.storageUpdating, 0, this.storageUpdating = allocateBytes(), 0, ARRAY_SIZE);
319 | }
320 |
321 | if (this.stateUpdating != INIT_STATE_HIDDEN) {
322 | this.stateUpdating = INIT_STATE_INIT;
323 | }
324 | this.updatingDirty = true;
325 | }
326 |
327 | // operation type: updating
328 | public boolean updateVisible() {
329 | if (!this.isDirty()) {
330 | return false;
331 | }
332 |
333 | synchronized (this) {
334 | if (this.stateUpdating == INIT_STATE_NULL || this.stateUpdating == INIT_STATE_UNINIT) {
335 | this.storageVisible = null;
336 | } else {
337 | if (this.storageVisible == null) {
338 | this.storageVisible = this.storageUpdating.clone();
339 | } else {
340 | if (this.storageUpdating != this.storageVisible) {
341 | System.arraycopy(this.storageUpdating, 0, this.storageVisible, 0, ARRAY_SIZE);
342 | }
343 | }
344 |
345 | if (this.storageUpdating != this.storageVisible) {
346 | freeBytes(this.storageUpdating);
347 | }
348 | this.storageUpdating = this.storageVisible;
349 | }
350 | this.updatingDirty = false;
351 | this.stateVisible = this.stateUpdating;
352 | }
353 |
354 | return true;
355 | }
356 |
357 | // operation type: visible
358 | public DataLayer toVanillaNibble() {
359 | synchronized (this) {
360 | switch (this.stateVisible) {
361 | case INIT_STATE_HIDDEN:
362 | case INIT_STATE_NULL:
363 | return null;
364 | case INIT_STATE_UNINIT:
365 | return new DataLayer();
366 | case INIT_STATE_INIT:
367 | return new DataLayer(this.storageVisible.clone());
368 | default:
369 | throw new IllegalStateException();
370 | }
371 | }
372 | }
373 |
374 | /* x | (z << 4) | (y << 8) */
375 |
376 | // operation type: updating
377 | public int getUpdating(final int x, final int y, final int z) {
378 | return this.getUpdating((x & 15) | ((z & 15) << 4) | ((y & 15) << 8));
379 | }
380 |
381 | // operation type: updating
382 | public int getUpdating(final int index) {
383 | // indices range from 0 -> 4096
384 | final byte[] bytes = this.storageUpdating;
385 | if (bytes == null) {
386 | return 0;
387 | }
388 | final byte value = bytes[index >>> 1];
389 |
390 | // if we are an even index, we want lower 4 bits
391 | // if we are an odd index, we want upper 4 bits
392 | return ((value >>> ((index & 1) << 2)) & 0xF);
393 | }
394 |
395 | // operation type: visible
396 | public int getVisible(final int x, final int y, final int z) {
397 | return this.getVisible((x & 15) | ((z & 15) << 4) | ((y & 15) << 8));
398 | }
399 |
400 | // operation type: visible
401 | public int getVisible(final int index) {
402 | // indices range from 0 -> 4096
403 | final byte[] visibleBytes = this.storageVisible;
404 | if (visibleBytes == null) {
405 | return 0;
406 | }
407 | final byte value = visibleBytes[index >>> 1];
408 |
409 | // if we are an even index, we want lower 4 bits
410 | // if we are an odd index, we want upper 4 bits
411 | return ((value >>> ((index & 1) << 2)) & 0xF);
412 | }
413 |
414 | // operation type: updating
415 | public void set(final int x, final int y, final int z, final int value) {
416 | this.set((x & 15) | ((z & 15) << 4) | ((y & 15) << 8), value);
417 | }
418 |
419 | // operation type: updating
420 | public void set(final int index, final int value) {
421 | if (!this.updatingDirty) {
422 | this.swapUpdatingAndMarkDirty();
423 | }
424 | final int shift = (index & 1) << 2;
425 | final int i = index >>> 1;
426 |
427 | this.storageUpdating[i] = (byte)((this.storageUpdating[i] & (0xF0 >>> shift)) | (value << shift));
428 | }
429 |
430 | public static final class SaveState {
431 |
432 | public final byte[] data;
433 | public final int state;
434 |
435 | public SaveState(final byte[] data, final int state) {
436 | this.data = data;
437 | this.state = state;
438 | }
439 | }
440 | }
441 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.common.light;
2 |
3 | import ca.spottedleaf.starlight.common.chunk.ExtendedChunk;
4 | import ca.spottedleaf.starlight.common.thread.GlobalExecutors;
5 | import ca.spottedleaf.starlight.common.thread.SchedulingUtil;
6 | import ca.spottedleaf.starlight.common.util.CoordinateUtils;
7 | import ca.spottedleaf.starlight.common.util.WorldUtil;
8 | import ca.spottedleaf.starlight.common.world.ExtendedWorld;
9 | import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
10 | import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
11 | import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
12 | import it.unimi.dsi.fastutil.longs.Long2ReferenceMaps;
13 | import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
14 | import it.unimi.dsi.fastutil.objects.ObjectBidirectionalIterator;
15 | import it.unimi.dsi.fastutil.shorts.ShortCollection;
16 | import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet;
17 | import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
18 | import net.minecraft.core.BlockPos;
19 | import net.minecraft.core.SectionPos;
20 | import net.minecraft.server.level.ServerLevel;
21 | import net.minecraft.server.level.ThreadedLevelLightEngine;
22 | import net.minecraft.server.level.TicketType;
23 | import net.minecraft.world.level.ChunkPos;
24 | import net.minecraft.world.level.Level;
25 | import net.minecraft.world.level.chunk.ChunkAccess;
26 | import net.minecraft.world.level.chunk.status.ChunkStatus;
27 | import net.minecraft.world.level.chunk.DataLayer;
28 | import net.minecraft.world.level.chunk.LightChunkGetter;
29 | import net.minecraft.world.level.lighting.LayerLightEventListener;
30 | import net.minecraft.world.level.lighting.LevelLightEngine;
31 | import java.util.ArrayDeque;
32 | import java.util.ArrayList;
33 | import java.util.List;
34 | import java.util.Set;
35 | import java.util.concurrent.CompletableFuture;
36 | import java.util.concurrent.atomic.AtomicInteger;
37 | import java.util.function.Consumer;
38 | import java.util.function.Function;
39 | import java.util.function.IntConsumer;
40 |
41 | public final class StarLightInterface {
42 |
43 | public static final TicketType CHUNK_WORK_TICKET = TicketType.create("starlight_chunk_work_ticket", (p1, p2) -> Long.compare(p1.toLong(), p2.toLong()));
44 |
45 | /**
46 | * Can be {@code null}, indicating the light is all empty.
47 | */
48 | protected final Level world;
49 | protected final LightChunkGetter lightAccess;
50 |
51 | protected final ArrayDeque cachedSkyPropagators;
52 | protected final ArrayDeque cachedBlockPropagators;
53 |
54 | protected final LightQueue lightQueue = new LightQueue(this);
55 |
56 | protected final LayerLightEventListener skyReader;
57 | protected final LayerLightEventListener blockReader;
58 | protected final boolean isClientSide;
59 |
60 | protected final int minSection;
61 | protected final int maxSection;
62 | protected final int minLightSection;
63 | protected final int maxLightSection;
64 |
65 | public final LevelLightEngine lightEngine;
66 |
67 | private final boolean hasBlockLight;
68 | private final boolean hasSkyLight;
69 |
70 | public StarLightInterface(final LightChunkGetter lightAccess, final boolean hasSkyLight, final boolean hasBlockLight, final LevelLightEngine lightEngine) {
71 | this.lightAccess = lightAccess;
72 | this.world = lightAccess == null ? null : (Level)lightAccess.getLevel();
73 | this.cachedSkyPropagators = hasSkyLight && lightAccess != null ? new ArrayDeque<>() : null;
74 | this.cachedBlockPropagators = hasBlockLight && lightAccess != null ? new ArrayDeque<>() : null;
75 | this.isClientSide = !(this.world instanceof ServerLevel);
76 | if (this.world == null) {
77 | this.minSection = -4;
78 | this.maxSection = 19;
79 | this.minLightSection = -5;
80 | this.maxLightSection = 20;
81 | } else {
82 | this.minSection = WorldUtil.getMinSection(this.world);
83 | this.maxSection = WorldUtil.getMaxSection(this.world);
84 | this.minLightSection = WorldUtil.getMinLightSection(this.world);
85 | this.maxLightSection = WorldUtil.getMaxLightSection(this.world);
86 | }
87 | this.lightEngine = lightEngine;
88 | this.hasBlockLight = hasBlockLight;
89 | this.hasSkyLight = hasSkyLight;
90 | this.skyReader = !hasSkyLight ? LayerLightEventListener.DummyLightLayerEventListener.INSTANCE : new LayerLightEventListener() {
91 | @Override
92 | public void checkBlock(final BlockPos blockPos) {
93 | StarLightInterface.this.lightEngine.checkBlock(blockPos.immutable());
94 | }
95 |
96 | @Override
97 | public void propagateLightSources(final ChunkPos chunkPos) {
98 | throw new UnsupportedOperationException();
99 | }
100 |
101 | @Override
102 | public boolean hasLightWork() {
103 | // not really correct...
104 | return StarLightInterface.this.hasUpdates();
105 | }
106 |
107 | @Override
108 | public int runLightUpdates() {
109 | throw new UnsupportedOperationException();
110 | }
111 |
112 | @Override
113 | public void setLightEnabled(final ChunkPos chunkPos, final boolean bl) {
114 | throw new UnsupportedOperationException();
115 | }
116 |
117 | @Override
118 | public DataLayer getDataLayerData(final SectionPos pos) {
119 | final ChunkAccess chunk = StarLightInterface.this.getAnyChunkNow(pos.getX(), pos.getZ());
120 | if (chunk == null || (!StarLightInterface.this.isClientSide && !chunk.isLightCorrect())) {
121 | return null;
122 | }
123 |
124 | final int sectionY = pos.getY();
125 |
126 | if (sectionY > StarLightInterface.this.maxLightSection || sectionY < StarLightInterface.this.minLightSection) {
127 | return null;
128 | }
129 |
130 | // if (((ExtendedChunk)chunk).getSkyEmptinessMap() == null) {
131 | // return null;
132 | // }
133 |
134 | return ((ExtendedChunk)chunk).getSkyNibbles()[sectionY - StarLightInterface.this.minLightSection].toVanillaNibble();
135 | }
136 |
137 | @Override
138 | public int getLightValue(final BlockPos blockPos) {
139 | return StarLightInterface.this.getSkyLightValue(blockPos, StarLightInterface.this.getAnyChunkNow(blockPos.getX() >> 4, blockPos.getZ() >> 4));
140 | }
141 |
142 | @Override
143 | public void updateSectionStatus(final SectionPos pos, final boolean notReady) {
144 | StarLightInterface.this.sectionChange(pos, notReady);
145 | }
146 | };
147 | this.blockReader = !hasBlockLight ? LayerLightEventListener.DummyLightLayerEventListener.INSTANCE : new LayerLightEventListener() {
148 | @Override
149 | public void checkBlock(final BlockPos blockPos) {
150 | StarLightInterface.this.lightEngine.checkBlock(blockPos.immutable());
151 | }
152 |
153 | @Override
154 | public void propagateLightSources(final ChunkPos chunkPos) {
155 | throw new UnsupportedOperationException();
156 | }
157 |
158 | @Override
159 | public boolean hasLightWork() {
160 | // not really correct...
161 | return StarLightInterface.this.hasUpdates();
162 | }
163 |
164 | @Override
165 | public int runLightUpdates() {
166 | throw new UnsupportedOperationException();
167 | }
168 |
169 | @Override
170 | public void setLightEnabled(final ChunkPos chunkPos, final boolean bl) {
171 | throw new UnsupportedOperationException();
172 | }
173 |
174 | @Override
175 | public DataLayer getDataLayerData(final SectionPos pos) {
176 | final ChunkAccess chunk = StarLightInterface.this.getAnyChunkNow(pos.getX(), pos.getZ());
177 |
178 | if (chunk == null || pos.getY() < StarLightInterface.this.minLightSection || pos.getY() > StarLightInterface.this.maxLightSection) {
179 | return null;
180 | }
181 |
182 | return ((ExtendedChunk)chunk).getBlockNibbles()[pos.getY() - StarLightInterface.this.minLightSection].toVanillaNibble();
183 | }
184 |
185 | @Override
186 | public int getLightValue(final BlockPos blockPos) {
187 | return StarLightInterface.this.getBlockLightValue(blockPos, StarLightInterface.this.getAnyChunkNow(blockPos.getX() >> 4, blockPos.getZ() >> 4));
188 | }
189 |
190 | @Override
191 | public void updateSectionStatus(final SectionPos pos, final boolean notReady) {
192 | StarLightInterface.this.sectionChange(pos, notReady);
193 | }
194 | };
195 | }
196 |
197 | public boolean hasSkyLight() {
198 | return this.hasSkyLight;
199 | }
200 |
201 | public boolean hasBlockLight() {
202 | return this.hasBlockLight;
203 | }
204 |
205 | public int getSkyLightValue(final BlockPos blockPos, final ChunkAccess chunk) {
206 | if (!this.hasSkyLight) {
207 | return 0;
208 | }
209 | final int x = blockPos.getX();
210 | int y = blockPos.getY();
211 | final int z = blockPos.getZ();
212 |
213 | final int minSection = this.minSection;
214 | final int maxSection = this.maxSection;
215 | final int minLightSection = this.minLightSection;
216 | final int maxLightSection = this.maxLightSection;
217 |
218 | if (chunk == null || (!this.isClientSide && !chunk.isLightCorrect()) || !chunk.getPersistedStatus().isOrAfter(ChunkStatus.LIGHT)) {
219 | return 15;
220 | }
221 |
222 | int sectionY = y >> 4;
223 |
224 | if (sectionY > maxLightSection) {
225 | return 15;
226 | }
227 |
228 | if (sectionY < minLightSection) {
229 | sectionY = minLightSection;
230 | y = sectionY << 4;
231 | }
232 |
233 | final SWMRNibbleArray[] nibbles = ((ExtendedChunk)chunk).getSkyNibbles();
234 | final SWMRNibbleArray immediate = nibbles[sectionY - minLightSection];
235 |
236 | if (!immediate.isNullNibbleVisible()) {
237 | return immediate.getVisible(x, y, z);
238 | }
239 |
240 | final boolean[] emptinessMap = ((ExtendedChunk)chunk).getSkyEmptinessMap();
241 |
242 | if (emptinessMap == null) {
243 | return 15;
244 | }
245 |
246 | // are we above this chunk's lowest empty section?
247 | int lowestY = minLightSection - 1;
248 | for (int currY = maxSection; currY >= minSection; --currY) {
249 | if (emptinessMap[currY - minSection]) {
250 | continue;
251 | }
252 |
253 | // should always be full lit here
254 | lowestY = currY;
255 | break;
256 | }
257 |
258 | if (sectionY > lowestY) {
259 | return 15;
260 | }
261 |
262 | // this nibble is going to depend solely on the skylight data above it
263 | // find first non-null data above (there does exist one, as we just found it above)
264 | for (int currY = sectionY + 1; currY <= maxLightSection; ++currY) {
265 | final SWMRNibbleArray nibble = nibbles[currY - minLightSection];
266 | if (!nibble.isNullNibbleVisible()) {
267 | return nibble.getVisible(x, 0, z);
268 | }
269 | }
270 |
271 | // should never reach here
272 | return 15;
273 | }
274 |
275 | public int getBlockLightValue(final BlockPos blockPos, final ChunkAccess chunk) {
276 | if (!this.hasBlockLight) {
277 | return 0;
278 | }
279 | final int y = blockPos.getY();
280 | final int cy = y >> 4;
281 |
282 | final int minLightSection = this.minLightSection;
283 | final int maxLightSection = this.maxLightSection;
284 |
285 | if (cy < minLightSection || cy > maxLightSection) {
286 | return 0;
287 | }
288 |
289 | if (chunk == null) {
290 | return 0;
291 | }
292 |
293 | final SWMRNibbleArray nibble = ((ExtendedChunk)chunk).getBlockNibbles()[cy - minLightSection];
294 | return nibble.getVisible(blockPos.getX(), y, blockPos.getZ());
295 | }
296 |
297 | public int getRawBrightness(final BlockPos pos, final int ambientDarkness) {
298 | final ChunkAccess chunk = this.getAnyChunkNow(pos.getX() >> 4, pos.getZ() >> 4);
299 |
300 | final int sky = this.getSkyLightValue(pos, chunk) - ambientDarkness;
301 | // Don't fetch the block light level if the skylight level is 15, since the value will never be higher.
302 | if (sky == 15) {
303 | return 15;
304 | }
305 | final int block = this.getBlockLightValue(pos, chunk);
306 | return Math.max(sky, block);
307 | }
308 |
309 | public LayerLightEventListener getSkyReader() {
310 | return this.skyReader;
311 | }
312 |
313 | public LayerLightEventListener getBlockReader() {
314 | return this.blockReader;
315 | }
316 |
317 | public boolean isClientSide() {
318 | return this.isClientSide;
319 | }
320 |
321 | public ChunkAccess getAnyChunkNow(final int chunkX, final int chunkZ) {
322 | if (this.world == null) {
323 | // empty world
324 | return null;
325 | }
326 | return ((ExtendedWorld)this.world).getAnyChunkImmediately(chunkX, chunkZ);
327 | }
328 |
329 | public boolean hasUpdates() {
330 | return !this.lightQueue.isEmpty();
331 | }
332 |
333 | public Level getWorld() {
334 | return this.world;
335 | }
336 |
337 | public LightChunkGetter getLightAccess() {
338 | return this.lightAccess;
339 | }
340 |
341 | protected final SkyStarLightEngine getSkyLightEngine() {
342 | if (this.cachedSkyPropagators == null) {
343 | return null;
344 | }
345 | final SkyStarLightEngine ret;
346 | synchronized (this.cachedSkyPropagators) {
347 | ret = this.cachedSkyPropagators.pollFirst();
348 | }
349 |
350 | if (ret == null) {
351 | return new SkyStarLightEngine(this.world);
352 | }
353 | return ret;
354 | }
355 |
356 | protected final void releaseSkyLightEngine(final SkyStarLightEngine engine) {
357 | if (this.cachedSkyPropagators == null) {
358 | return;
359 | }
360 | synchronized (this.cachedSkyPropagators) {
361 | this.cachedSkyPropagators.addFirst(engine);
362 | }
363 | }
364 |
365 | protected final BlockStarLightEngine getBlockLightEngine() {
366 | if (this.cachedBlockPropagators == null) {
367 | return null;
368 | }
369 | final BlockStarLightEngine ret;
370 | synchronized (this.cachedBlockPropagators) {
371 | ret = this.cachedBlockPropagators.pollFirst();
372 | }
373 |
374 | if (ret == null) {
375 | return new BlockStarLightEngine(this.world);
376 | }
377 | return ret;
378 | }
379 |
380 | protected final void releaseBlockLightEngine(final BlockStarLightEngine engine) {
381 | if (this.cachedBlockPropagators == null) {
382 | return;
383 | }
384 | synchronized (this.cachedBlockPropagators) {
385 | this.cachedBlockPropagators.addFirst(engine);
386 | }
387 | }
388 |
389 | public LightQueue.ChunkTasks blockChange(final BlockPos pos) {
390 | if (this.world == null || pos.getY() < WorldUtil.getMinBlockY(this.world) || pos.getY() > WorldUtil.getMaxBlockY(this.world)) { // empty world
391 | return null;
392 | }
393 |
394 | return this.lightQueue.queueBlockChange(pos);
395 | }
396 |
397 | public LightQueue.ChunkTasks sectionChange(final SectionPos pos, final boolean newEmptyValue) {
398 | if (this.world == null) { // empty world
399 | return null;
400 | }
401 |
402 | return this.lightQueue.queueSectionChange(pos, newEmptyValue);
403 | }
404 |
405 | public void forceLoadInChunk(final ChunkAccess chunk, final Boolean[] emptySections) {
406 | final SkyStarLightEngine skyEngine = this.getSkyLightEngine();
407 | final BlockStarLightEngine blockEngine = this.getBlockLightEngine();
408 |
409 | try {
410 | if (skyEngine != null) {
411 | skyEngine.forceHandleEmptySectionChanges(this.lightAccess, chunk, emptySections);
412 | }
413 | if (blockEngine != null) {
414 | blockEngine.forceHandleEmptySectionChanges(this.lightAccess, chunk, emptySections);
415 | }
416 | } finally {
417 | this.releaseSkyLightEngine(skyEngine);
418 | this.releaseBlockLightEngine(blockEngine);
419 | }
420 | }
421 |
422 | public void loadInChunk(final int chunkX, final int chunkZ, final Boolean[] emptySections) {
423 | final SkyStarLightEngine skyEngine = this.getSkyLightEngine();
424 | final BlockStarLightEngine blockEngine = this.getBlockLightEngine();
425 |
426 | try {
427 | if (skyEngine != null) {
428 | skyEngine.handleEmptySectionChanges(this.lightAccess, chunkX, chunkZ, emptySections);
429 | }
430 | if (blockEngine != null) {
431 | blockEngine.handleEmptySectionChanges(this.lightAccess, chunkX, chunkZ, emptySections);
432 | }
433 | } finally {
434 | this.releaseSkyLightEngine(skyEngine);
435 | this.releaseBlockLightEngine(blockEngine);
436 | }
437 | }
438 |
439 | public void lightChunk(final ChunkAccess chunk, final Boolean[] emptySections) {
440 | final SkyStarLightEngine skyEngine = this.getSkyLightEngine();
441 | final BlockStarLightEngine blockEngine = this.getBlockLightEngine();
442 |
443 | try {
444 | if (skyEngine != null) {
445 | skyEngine.light(this.lightAccess, chunk, emptySections);
446 | }
447 | if (blockEngine != null) {
448 | blockEngine.light(this.lightAccess, chunk, emptySections);
449 | }
450 | } finally {
451 | this.releaseSkyLightEngine(skyEngine);
452 | this.releaseBlockLightEngine(blockEngine);
453 | }
454 | }
455 |
456 | public void relightChunks(final Set chunks, final Consumer chunkLightCallback,
457 | final IntConsumer onComplete) {
458 | final SkyStarLightEngine skyEngine = this.getSkyLightEngine();
459 | final BlockStarLightEngine blockEngine = this.getBlockLightEngine();
460 |
461 | try {
462 | if (skyEngine != null) {
463 | skyEngine.relightChunks(this.lightAccess, chunks, blockEngine == null ? chunkLightCallback : null,
464 | blockEngine == null ? onComplete : null);
465 | }
466 | if (blockEngine != null) {
467 | blockEngine.relightChunks(this.lightAccess, chunks, chunkLightCallback, onComplete);
468 | }
469 | } finally {
470 | this.releaseSkyLightEngine(skyEngine);
471 | this.releaseBlockLightEngine(blockEngine);
472 | }
473 | }
474 |
475 | public void checkChunkEdges(final int chunkX, final int chunkZ) {
476 | this.checkSkyEdges(chunkX, chunkZ);
477 | this.checkBlockEdges(chunkX, chunkZ);
478 | }
479 |
480 | public void checkSkyEdges(final int chunkX, final int chunkZ) {
481 | final SkyStarLightEngine skyEngine = this.getSkyLightEngine();
482 |
483 | try {
484 | if (skyEngine != null) {
485 | skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ);
486 | }
487 | } finally {
488 | this.releaseSkyLightEngine(skyEngine);
489 | }
490 | }
491 |
492 | public void checkBlockEdges(final int chunkX, final int chunkZ) {
493 | final BlockStarLightEngine blockEngine = this.getBlockLightEngine();
494 | try {
495 | if (blockEngine != null) {
496 | blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ);
497 | }
498 | } finally {
499 | this.releaseBlockLightEngine(blockEngine);
500 | }
501 | }
502 |
503 | public void checkSkyEdges(final int chunkX, final int chunkZ, final ShortCollection sections) {
504 | final SkyStarLightEngine skyEngine = this.getSkyLightEngine();
505 |
506 | try {
507 | if (skyEngine != null) {
508 | skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, sections);
509 | }
510 | } finally {
511 | this.releaseSkyLightEngine(skyEngine);
512 | }
513 | }
514 |
515 | public void checkBlockEdges(final int chunkX, final int chunkZ, final ShortCollection sections) {
516 | final BlockStarLightEngine blockEngine = this.getBlockLightEngine();
517 | try {
518 | if (blockEngine != null) {
519 | blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, sections);
520 | }
521 | } finally {
522 | this.releaseBlockLightEngine(blockEngine);
523 | }
524 | }
525 |
526 | public void scheduleChunkLight(final ChunkPos pos, final Runnable run) {
527 | this.lightQueue.queueChunkLighting(pos, run);
528 | }
529 |
530 | public CompletableFuture syncFuture(final int chunkX, final int chunkZ) {
531 | return this.lightQueue.getChunkSyncFuture(chunkX, chunkZ).thenApply(Function.identity());
532 | }
533 |
534 | public void removeChunkTasks(final ChunkPos pos) {
535 | this.lightQueue.removeChunk(pos);
536 | }
537 |
538 | public void propagateChanges() {
539 | if (this.lightQueue.isEmpty()) {
540 | return;
541 | }
542 |
543 | if (GlobalExecutors.ENABLED && this.lightEngine instanceof ThreadedLevelLightEngine threadedLevelLightEngine) {
544 | this.schedulePropagation0(threadedLevelLightEngine);
545 | return;
546 | }
547 |
548 | final SkyStarLightEngine skyEngine = this.getSkyLightEngine();
549 | final BlockStarLightEngine blockEngine = this.getBlockLightEngine();
550 |
551 | try {
552 | LightQueue.ChunkTasks task;
553 | while ((task = this.lightQueue.removeFirstTask()) != null) {
554 | handleUpdateInternal(task, skyEngine, blockEngine);
555 | }
556 | } finally {
557 | this.releaseSkyLightEngine(skyEngine);
558 | this.releaseBlockLightEngine(blockEngine);
559 | }
560 | }
561 |
562 | private static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger(0);
563 | private static final CompletableFuture COMPLETED_FUTURE = CompletableFuture.completedFuture(null);
564 | private final int instanceId = INSTANCE_COUNTER.getAndIncrement();
565 | private final Long2ReferenceMap> chunkFutures = Long2ReferenceMaps.synchronize(new Long2ReferenceOpenHashMap<>());
566 |
567 | private void schedulePropagation0(ThreadedLevelLightEngine threadedLevelLightEngine) {
568 | synchronized (this.lightQueue) {
569 | final ObjectBidirectionalIterator> iterator = this.lightQueue.chunkTasks.long2ObjectEntrySet().fastIterator();
570 | while (iterator.hasNext()) {
571 | final Long2ObjectMap.Entry entry = iterator.next();
572 | final long pos = entry.getLongKey();
573 | if (!this.chunkFutures.getOrDefault(pos, COMPLETED_FUTURE).isDone()) {
574 | continue;
575 | }
576 | CompletableFuture future = new CompletableFuture<>();
577 | SchedulingUtil.scheduleTask(
578 | this.instanceId,
579 | () -> {
580 | try {
581 | final SkyStarLightEngine skyEngine = this.getSkyLightEngine();
582 | final BlockStarLightEngine blockEngine = this.getBlockLightEngine();
583 |
584 | LightQueue.ChunkTasks tasks;
585 | synchronized (this.lightQueue) {
586 | tasks = this.lightQueue.chunkTasks.remove(pos);
587 | }
588 | if (tasks != null) {
589 | try {
590 | handleUpdateInternal(tasks, skyEngine, blockEngine);
591 | } finally {
592 | this.releaseSkyLightEngine(skyEngine);
593 | this.releaseBlockLightEngine(blockEngine);
594 | }
595 |
596 | threadedLevelLightEngine.tryScheduleUpdate();
597 | }
598 | future.complete(null);
599 | } catch (Throwable t) {
600 | future.completeExceptionally(t);
601 | t.printStackTrace();
602 | }
603 | },
604 | CoordinateUtils.getChunkX(pos),
605 | CoordinateUtils.getChunkZ(pos),
606 | 2
607 | );
608 | chunkFutures.put(pos, future);
609 | future.whenComplete((unused, throwable) -> this.chunkFutures.remove(pos, future));
610 | }
611 | this.lightQueue.queueDirty = false;
612 | }
613 | }
614 |
615 | /**
616 | * Only relevant on server lighting with scaling enabled, best-effort check if the queue is dirty.
617 | */
618 | public boolean isQueueDirty() {
619 | return this.lightQueue.queueDirty;
620 | }
621 |
622 | private void handleUpdateInternal(LightQueue.ChunkTasks task, SkyStarLightEngine skyEngine, BlockStarLightEngine blockEngine) { // keep indentation
623 | if (task.lightTasks != null) {
624 | for (final Runnable run : task.lightTasks) {
625 | run.run();
626 | }
627 | }
628 |
629 | final long coordinate = task.chunkCoordinate;
630 | final int chunkX = CoordinateUtils.getChunkX(coordinate);
631 | final int chunkZ = CoordinateUtils.getChunkZ(coordinate);
632 |
633 | final Set positions = task.changedPositions;
634 | final Boolean[] sectionChanges = task.changedSectionSet;
635 |
636 | if (skyEngine != null && (!positions.isEmpty() || sectionChanges != null)) {
637 | skyEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges);
638 | }
639 | if (blockEngine != null && (!positions.isEmpty() || sectionChanges != null)) {
640 | blockEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges);
641 | }
642 |
643 | if (skyEngine != null && task.queuedEdgeChecksSky != null) {
644 | skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, task.queuedEdgeChecksSky);
645 | }
646 | if (blockEngine != null && task.queuedEdgeChecksBlock != null) {
647 | blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, task.queuedEdgeChecksBlock);
648 | }
649 |
650 | task.onComplete.complete(null);
651 | }
652 |
653 | public static final class LightQueue {
654 |
655 | protected final Long2ObjectLinkedOpenHashMap chunkTasks = new Long2ObjectLinkedOpenHashMap<>();
656 | protected final StarLightInterface manager;
657 | protected volatile boolean queueDirty = false;
658 |
659 | public LightQueue(final StarLightInterface manager) {
660 | this.manager = manager;
661 | }
662 |
663 | public synchronized boolean isEmpty() {
664 | return this.chunkTasks.isEmpty();
665 | }
666 |
667 | public synchronized LightQueue.ChunkTasks queueBlockChange(final BlockPos pos) {
668 | final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new);
669 | tasks.changedPositions.add(pos.immutable());
670 | this.queueDirty = true;
671 | return tasks;
672 | }
673 |
674 | public synchronized LightQueue.ChunkTasks queueSectionChange(final SectionPos pos, final boolean newEmptyValue) {
675 | final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new);
676 |
677 | if (tasks.changedSectionSet == null) {
678 | tasks.changedSectionSet = new Boolean[this.manager.maxSection - this.manager.minSection + 1];
679 | }
680 | tasks.changedSectionSet[pos.getY() - this.manager.minSection] = Boolean.valueOf(newEmptyValue);
681 |
682 | this.queueDirty = true;
683 | return tasks;
684 | }
685 |
686 | public synchronized LightQueue.ChunkTasks queueChunkLighting(final ChunkPos pos, final Runnable lightTask) {
687 | final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new);
688 | if (tasks.lightTasks == null) {
689 | tasks.lightTasks = new ArrayList<>();
690 | }
691 | tasks.lightTasks.add(lightTask);
692 |
693 | this.queueDirty = true;
694 | return tasks;
695 | }
696 |
697 | public synchronized LightQueue.ChunkTasks queueChunkSkylightEdgeCheck(final SectionPos pos, final ShortCollection sections) {
698 | final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new);
699 |
700 | ShortOpenHashSet queuedEdges = tasks.queuedEdgeChecksSky;
701 | if (queuedEdges == null) {
702 | queuedEdges = tasks.queuedEdgeChecksSky = new ShortOpenHashSet();
703 | }
704 | queuedEdges.addAll(sections);
705 |
706 | this.queueDirty = true;
707 | return tasks;
708 | }
709 |
710 | public synchronized LightQueue.ChunkTasks queueChunkBlocklightEdgeCheck(final SectionPos pos, final ShortCollection sections) {
711 | final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new);
712 |
713 | ShortOpenHashSet queuedEdges = tasks.queuedEdgeChecksBlock;
714 | if (queuedEdges == null) {
715 | queuedEdges = tasks.queuedEdgeChecksBlock = new ShortOpenHashSet();
716 | }
717 | queuedEdges.addAll(sections);
718 |
719 | this.queueDirty = true;
720 | return tasks;
721 | }
722 |
723 | public synchronized CompletableFuture getChunkSyncFuture(final int chunkX, final int chunkZ) {
724 | final ChunkTasks tasks = this.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
725 | if (tasks == null) {
726 | return CompletableFuture.completedFuture(null);
727 | } else {
728 | return tasks.onComplete;
729 | }
730 | }
731 |
732 | public void removeChunk(final ChunkPos pos) {
733 | final ChunkTasks tasks;
734 | synchronized (this) {
735 | tasks = this.chunkTasks.remove(CoordinateUtils.getChunkKey(pos));
736 | }
737 | if (tasks != null) {
738 | tasks.onComplete.complete(null);
739 | }
740 | this.queueDirty = true;
741 | }
742 |
743 | public synchronized ChunkTasks removeFirstTask() {
744 | if (this.chunkTasks.isEmpty()) {
745 | return null;
746 | }
747 | return this.chunkTasks.removeFirst();
748 | }
749 |
750 | public static final class ChunkTasks {
751 |
752 | public final Set changedPositions = new ObjectOpenHashSet<>();
753 | public Boolean[] changedSectionSet;
754 | public ShortOpenHashSet queuedEdgeChecksSky;
755 | public ShortOpenHashSet queuedEdgeChecksBlock;
756 | public List lightTasks;
757 |
758 | public boolean isTicketAdded = false;
759 | public final CompletableFuture onComplete = new CompletableFuture<>();
760 |
761 | public final long chunkCoordinate;
762 |
763 | public ChunkTasks(final long chunkCoordinate) {
764 | this.chunkCoordinate = chunkCoordinate;
765 | }
766 | }
767 | }
768 | }
769 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/common/light/StarLightLightingProvider.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.common.light;
2 |
3 | import net.minecraft.core.SectionPos;
4 | import net.minecraft.world.level.ChunkPos;
5 | import net.minecraft.world.level.LightLayer;
6 | import net.minecraft.world.level.chunk.DataLayer;
7 | import net.minecraft.world.level.chunk.LevelChunk;
8 |
9 | public interface StarLightLightingProvider {
10 |
11 | public StarLightInterface getLightEngine();
12 |
13 | public void clientUpdateLight(final LightLayer lightType, final SectionPos pos,
14 | final DataLayer nibble, final boolean trustEdges);
15 |
16 | public void clientRemoveLightData(final ChunkPos chunkPos);
17 |
18 | public void clientChunkLoad(final ChunkPos pos, final LevelChunk chunk);
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/common/thread/GlobalExecutors.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.common.thread;
2 |
3 | import ca.spottedleaf.starlight.common.config.Config;
4 | import com.ishland.flowsched.executor.ExecutorManager;
5 |
6 | import java.util.concurrent.atomic.AtomicInteger;
7 |
8 | public class GlobalExecutors {
9 |
10 | private static final AtomicInteger prioritizedSchedulerCounter = new AtomicInteger(0);
11 | public static final ExecutorManager prioritizedScheduler = new ExecutorManager(Config.PARALLELISM, thread -> {
12 | thread.setDaemon(true);
13 | thread.setName("scalablelux-%d".formatted(prioritizedSchedulerCounter.getAndIncrement()));
14 | });
15 | private static final boolean FORCE_ENABLED = Boolean.getBoolean("scalablelux.force_enabled");
16 | public static final boolean ENABLED = SchedulingUtil.isExternallyManaged() || FORCE_ENABLED || Config.PARALLELISM > 1;
17 |
18 | static {
19 | if (SchedulingUtil.isExternallyManaged()) {
20 | System.out.println("[ScalableLux] Lighting scaling is enabled in externally managed mode");
21 | } else if (FORCE_ENABLED) {
22 | System.out.println("[ScalableLux] Lighting scaling is forced enabled, using %d threads".formatted(Config.PARALLELISM));
23 | } else if (ENABLED) {
24 | System.out.println("[ScalableLux] Lighting scaling is enabled, using %d threads".formatted(Config.PARALLELISM));
25 | } else {
26 | System.out.println("[ScalableLux] Lighting scaling is disabled (due to low parallelism in the settings)");
27 | }
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/common/thread/LockTokenImpl.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.common.thread;
2 |
3 | import com.ishland.flowsched.executor.LockToken;
4 |
5 | public record LockTokenImpl(int ownerTag, long pos) implements LockToken {
6 | }
7 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/common/thread/SchedulingUtil.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.common.thread;
2 |
3 | import ca.spottedleaf.starlight.common.util.CoordinateUtils;
4 | import com.ishland.flowsched.executor.LockToken;
5 |
6 | import java.util.ArrayList;
7 |
8 | public class SchedulingUtil {
9 |
10 | public static void scheduleTask(int ownerTag, Runnable task, int x, int z, int radius) {
11 | final ArrayList lockTokens = new ArrayList<>((radius * 2 + 1) * (radius * 2 + 1));
12 | for (int i = -radius; i <= radius; i++) {
13 | for (int j = -radius; j <= radius; j++) {
14 | lockTokens.add(new LockTokenImpl(ownerTag, CoordinateUtils.getChunkKey(x + i, z + j)));
15 | }
16 | }
17 | final SimpleTask simpleTask = new SimpleTask(task, lockTokens.toArray(LockToken[]::new), 60);
18 | GlobalExecutors.prioritizedScheduler.schedule(simpleTask);
19 | }
20 |
21 | public static boolean isExternallyManaged() {
22 | return false;
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/common/thread/SimpleTask.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.common.thread;
2 |
3 | import com.ishland.flowsched.executor.LockToken;
4 | import com.ishland.flowsched.executor.Task;
5 |
6 | import java.util.Objects;
7 |
8 | public class SimpleTask implements Task {
9 |
10 | private final Runnable task;
11 | private final LockToken[] lockTokens;
12 | private final int priority;
13 |
14 | public SimpleTask(Runnable task, LockToken[] lockTokens, int priority) {
15 | this.task = Objects.requireNonNull(task, "task");
16 | this.lockTokens = Objects.requireNonNull(lockTokens, "lockTokens");
17 | this.priority = priority;
18 | }
19 |
20 | @Override
21 | public void run(Runnable releaseLocks) {
22 | try {
23 | this.task.run();
24 | } finally {
25 | releaseLocks.run();
26 | }
27 | }
28 |
29 | @Override
30 | public void propagateException(Throwable t) {
31 | t.printStackTrace();
32 | }
33 |
34 | @Override
35 | public LockToken[] lockTokens() {
36 | return this.lockTokens;
37 | }
38 |
39 | @Override
40 | public int priority() {
41 | return this.priority;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/common/util/CoordinateUtils.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.common.util;
2 |
3 | import net.minecraft.core.BlockPos;
4 | import net.minecraft.core.SectionPos;
5 | import net.minecraft.util.Mth;
6 | import net.minecraft.world.entity.Entity;
7 | import net.minecraft.world.level.ChunkPos;
8 |
9 | public final class CoordinateUtils {
10 |
11 | // dx, dz are relative to the target chunk
12 | // dx, dz in [-radius, radius]
13 | public static int getNeighbourMappedIndex(final int dx, final int dz, final int radius) {
14 | return (dx + radius) + (2 * radius + 1)*(dz + radius);
15 | }
16 |
17 | // the chunk keys are compatible with vanilla
18 |
19 | public static long getChunkKey(final BlockPos pos) {
20 | return ((long)(pos.getZ() >> 4) << 32) | ((pos.getX() >> 4) & 0xFFFFFFFFL);
21 | }
22 |
23 | public static long getChunkKey(final Entity entity) {
24 | return ((long)(Mth.floor(entity.getZ()) >> 4) << 32) | ((Mth.floor(entity.getX()) >> 4) & 0xFFFFFFFFL);
25 | }
26 |
27 | public static long getChunkKey(final ChunkPos pos) {
28 | return ((long)pos.z << 32) | (pos.x & 0xFFFFFFFFL);
29 | }
30 |
31 | public static long getChunkKey(final SectionPos pos) {
32 | return ((long)pos.getZ() << 32) | (pos.getX() & 0xFFFFFFFFL);
33 | }
34 |
35 | public static long getChunkKey(final int x, final int z) {
36 | return ((long)z << 32) | (x & 0xFFFFFFFFL);
37 | }
38 |
39 | public static int getChunkX(final long chunkKey) {
40 | return (int)chunkKey;
41 | }
42 |
43 | public static int getChunkZ(final long chunkKey) {
44 | return (int)(chunkKey >>> 32);
45 | }
46 |
47 | public static int getChunkCoordinate(final double blockCoordinate) {
48 | return Mth.floor(blockCoordinate) >> 4;
49 | }
50 |
51 | // the section keys are compatible with vanilla's
52 |
53 | static final int SECTION_X_BITS = 22;
54 | static final long SECTION_X_MASK = (1L << SECTION_X_BITS) - 1;
55 | static final int SECTION_Y_BITS = 20;
56 | static final long SECTION_Y_MASK = (1L << SECTION_Y_BITS) - 1;
57 | static final int SECTION_Z_BITS = 22;
58 | static final long SECTION_Z_MASK = (1L << SECTION_Z_BITS) - 1;
59 | // format is y,z,x (in order of LSB to MSB)
60 | static final int SECTION_Y_SHIFT = 0;
61 | static final int SECTION_Z_SHIFT = SECTION_Y_SHIFT + SECTION_Y_BITS;
62 | static final int SECTION_X_SHIFT = SECTION_Z_SHIFT + SECTION_X_BITS;
63 | static final int SECTION_TO_BLOCK_SHIFT = 4;
64 |
65 | public static long getChunkSectionKey(final int x, final int y, final int z) {
66 | return ((x & SECTION_X_MASK) << SECTION_X_SHIFT)
67 | | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT)
68 | | ((z & SECTION_Z_MASK) << SECTION_Z_SHIFT);
69 | }
70 |
71 | public static long getChunkSectionKey(final SectionPos pos) {
72 | return ((pos.getX() & SECTION_X_MASK) << SECTION_X_SHIFT)
73 | | ((pos.getY() & SECTION_Y_MASK) << SECTION_Y_SHIFT)
74 | | ((pos.getZ() & SECTION_Z_MASK) << SECTION_Z_SHIFT);
75 | }
76 |
77 | public static long getChunkSectionKey(final ChunkPos pos, final int y) {
78 | return ((pos.x & SECTION_X_MASK) << SECTION_X_SHIFT)
79 | | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT)
80 | | ((pos.z & SECTION_Z_MASK) << SECTION_Z_SHIFT);
81 | }
82 |
83 | public static long getChunkSectionKey(final BlockPos pos) {
84 | return (((long)pos.getX() << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) |
85 | ((pos.getY() >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) |
86 | (((long)pos.getZ() << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT));
87 | }
88 |
89 | public static long getChunkSectionKey(final Entity entity) {
90 | return ((Mth.lfloor(entity.getX()) << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) |
91 | ((Mth.lfloor(entity.getY()) >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) |
92 | ((Mth.lfloor(entity.getZ()) << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT));
93 | }
94 |
95 | public static int getChunkSectionX(final long key) {
96 | return (int)(key << (Long.SIZE - (SECTION_X_SHIFT + SECTION_X_BITS)) >> (Long.SIZE - SECTION_X_BITS));
97 | }
98 |
99 | public static int getChunkSectionY(final long key) {
100 | return (int)(key << (Long.SIZE - (SECTION_Y_SHIFT + SECTION_Y_BITS)) >> (Long.SIZE - SECTION_Y_BITS));
101 | }
102 |
103 | public static int getChunkSectionZ(final long key) {
104 | return (int)(key << (Long.SIZE - (SECTION_Z_SHIFT + SECTION_Z_BITS)) >> (Long.SIZE - SECTION_Z_BITS));
105 | }
106 |
107 | // the block coordinates are not necessarily compatible with vanilla's
108 |
109 | public static int getBlockCoordinate(final double blockCoordinate) {
110 | return Mth.floor(blockCoordinate);
111 | }
112 |
113 | public static long getBlockKey(final int x, final int y, final int z) {
114 | return ((long)x & 0x7FFFFFF) | (((long)z & 0x7FFFFFF) << 27) | ((long)y << 54);
115 | }
116 |
117 | public static long getBlockKey(final BlockPos pos) {
118 | return ((long)pos.getX() & 0x7FFFFFF) | (((long)pos.getZ() & 0x7FFFFFF) << 27) | ((long)pos.getY() << 54);
119 | }
120 |
121 | public static long getBlockKey(final Entity entity) {
122 | return ((long)entity.getX() & 0x7FFFFFF) | (((long)entity.getZ() & 0x7FFFFFF) << 27) | ((long)entity.getY() << 54);
123 | }
124 |
125 | private CoordinateUtils() {
126 | throw new RuntimeException();
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/common/util/IntegerUtil.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.common.util;
2 |
3 | public final class IntegerUtil {
4 |
5 | public static final int HIGH_BIT_U32 = Integer.MIN_VALUE;
6 | public static final long HIGH_BIT_U64 = Long.MIN_VALUE;
7 |
8 | public static int ceilLog2(final int value) {
9 | return Integer.SIZE - Integer.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros
10 | }
11 |
12 | public static long ceilLog2(final long value) {
13 | return Long.SIZE - Long.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros
14 | }
15 |
16 | public static int floorLog2(final int value) {
17 | // xor is optimized subtract for 2^n -1
18 | // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1)
19 | return (Integer.SIZE - 1) ^ Integer.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros
20 | }
21 |
22 | public static int floorLog2(final long value) {
23 | // xor is optimized subtract for 2^n -1
24 | // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1)
25 | return (Long.SIZE - 1) ^ Long.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros
26 | }
27 |
28 | public static int roundCeilLog2(final int value) {
29 | // optimized variant of 1 << (32 - leading(val - 1))
30 | // given
31 | // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32)
32 | // 1 << (32 - leading(val - 1)) = HIGH_BIT_32 >>> (31 - (32 - leading(val - 1)))
33 | // HIGH_BIT_32 >>> (31 - (32 - leading(val - 1)))
34 | // HIGH_BIT_32 >>> (31 - 32 + leading(val - 1))
35 | // HIGH_BIT_32 >>> (-1 + leading(val - 1))
36 | return HIGH_BIT_U32 >>> (Integer.numberOfLeadingZeros(value - 1) - 1);
37 | }
38 |
39 | public static long roundCeilLog2(final long value) {
40 | // see logic documented above
41 | return HIGH_BIT_U64 >>> (Long.numberOfLeadingZeros(value - 1) - 1);
42 | }
43 |
44 | public static int roundFloorLog2(final int value) {
45 | // optimized variant of 1 << (31 - leading(val))
46 | // given
47 | // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32)
48 | // 1 << (31 - leading(val)) = HIGH_BIT_32 >> (31 - (31 - leading(val)))
49 | // HIGH_BIT_32 >> (31 - (31 - leading(val)))
50 | // HIGH_BIT_32 >> (31 - 31 + leading(val))
51 | return HIGH_BIT_U32 >>> Integer.numberOfLeadingZeros(value);
52 | }
53 |
54 | public static long roundFloorLog2(final long value) {
55 | // see logic documented above
56 | return HIGH_BIT_U64 >>> Long.numberOfLeadingZeros(value);
57 | }
58 |
59 | public static boolean isPowerOfTwo(final int n) {
60 | // 2^n has one bit
61 | // note: this rets true for 0 still
62 | return IntegerUtil.getTrailingBit(n) == n;
63 | }
64 |
65 | public static boolean isPowerOfTwo(final long n) {
66 | // 2^n has one bit
67 | // note: this rets true for 0 still
68 | return IntegerUtil.getTrailingBit(n) == n;
69 | }
70 |
71 | public static int getTrailingBit(final int n) {
72 | return -n & n;
73 | }
74 |
75 | public static long getTrailingBit(final long n) {
76 | return -n & n;
77 | }
78 |
79 | public static int trailingZeros(final int n) {
80 | return Integer.numberOfTrailingZeros(n);
81 | }
82 |
83 | public static int trailingZeros(final long n) {
84 | return Long.numberOfTrailingZeros(n);
85 | }
86 |
87 | // from hacker's delight (signed division magic value)
88 | public static int getDivisorMultiple(final long numbers) {
89 | return (int)(numbers >>> 32);
90 | }
91 |
92 | // from hacker's delight (signed division magic value)
93 | public static int getDivisorShift(final long numbers) {
94 | return (int)numbers;
95 | }
96 |
97 | // copied from hacker's delight (signed division magic value)
98 | // http://www.hackersdelight.org/hdcodetxt/magic.c.txt
99 | public static long getDivisorNumbers(final int d) {
100 | final int ad = branchlessAbs(d);
101 |
102 | if (ad < 2) {
103 | throw new IllegalArgumentException("|number| must be in [2, 2^31 -1], not: " + d);
104 | }
105 |
106 | final int two31 = 0x80000000;
107 | final long mask = 0xFFFFFFFFL; // mask for enforcing unsigned behaviour
108 |
109 | /*
110 | Signed usage:
111 | int number;
112 | long magic = getDivisorNumbers(div);
113 | long mul = magic >>> 32;
114 | int sign = number >> 31;
115 | int result = (int)(((long)number * mul) >>> magic) - sign;
116 | */
117 | /*
118 | Unsigned usage:
119 | int number;
120 | long magic = getDivisorNumbers(div);
121 | long mul = magic >>> 32;
122 | int result = (int)(((long)number * mul) >>> magic);
123 | */
124 |
125 | int p = 31;
126 |
127 | // all these variables are UNSIGNED!
128 | int t = two31 + (d >>> 31);
129 | int anc = t - 1 - (int)((t & mask)%ad);
130 | int q1 = (int)((two31 & mask)/(anc & mask));
131 | int r1 = two31 - q1*anc;
132 | int q2 = (int)((two31 & mask)/(ad & mask));
133 | int r2 = two31 - q2*ad;
134 | int delta;
135 |
136 | do {
137 | p = p + 1;
138 | q1 = 2*q1; // Update q1 = 2**p/|nc|.
139 | r1 = 2*r1; // Update r1 = rem(2**p, |nc|).
140 | if ((r1 & mask) >= (anc & mask)) {// (Must be an unsigned comparison here)
141 | q1 = q1 + 1;
142 | r1 = r1 - anc;
143 | }
144 | q2 = 2*q2; // Update q2 = 2**p/|d|.
145 | r2 = 2*r2; // Update r2 = rem(2**p, |d|).
146 | if ((r2 & mask) >= (ad & mask)) {// (Must be an unsigned comparison here)
147 | q2 = q2 + 1;
148 | r2 = r2 - ad;
149 | }
150 | delta = ad - r2;
151 | } while ((q1 & mask) < (delta & mask) || (q1 == delta && r1 == 0));
152 |
153 | int magicNum = q2 + 1;
154 | if (d < 0) {
155 | magicNum = -magicNum;
156 | }
157 | int shift = p;
158 | return ((long)magicNum << 32) | shift;
159 | }
160 |
161 | public static int branchlessAbs(final int val) {
162 | // -n = -1 ^ n + 1
163 | final int mask = val >> (Integer.SIZE - 1); // -1 if < 0, 0 if >= 0
164 | return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1
165 | }
166 |
167 | public static long branchlessAbs(final long val) {
168 | // -n = -1 ^ n + 1
169 | final long mask = val >> (Long.SIZE - 1); // -1 if < 0, 0 if >= 0
170 | return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1
171 | }
172 |
173 | //https://github.com/skeeto/hash-prospector for hash functions
174 |
175 | //score = ~590.47984224483832
176 | public static int hash0(int x) {
177 | x *= 0x36935555;
178 | x ^= x >>> 16;
179 | return x;
180 | }
181 |
182 | //score = ~310.01596637036749
183 | public static int hash1(int x) {
184 | x ^= x >>> 15;
185 | x *= 0x356aaaad;
186 | x ^= x >>> 17;
187 | return x;
188 | }
189 |
190 | public static int hash2(int x) {
191 | x ^= x >>> 16;
192 | x *= 0x7feb352d;
193 | x ^= x >>> 15;
194 | x *= 0x846ca68b;
195 | x ^= x >>> 16;
196 | return x;
197 | }
198 |
199 | public static int hash3(int x) {
200 | x ^= x >>> 17;
201 | x *= 0xed5ad4bb;
202 | x ^= x >>> 11;
203 | x *= 0xac4c1b51;
204 | x ^= x >>> 15;
205 | x *= 0x31848bab;
206 | x ^= x >>> 14;
207 | return x;
208 | }
209 |
210 | //score = ~365.79959673201887
211 | public static long hash1(long x) {
212 | x ^= x >>> 27;
213 | x *= 0xb24924b71d2d354bL;
214 | x ^= x >>> 28;
215 | return x;
216 | }
217 |
218 | //h2 hash
219 | public static long hash2(long x) {
220 | x ^= x >>> 32;
221 | x *= 0xd6e8feb86659fd93L;
222 | x ^= x >>> 32;
223 | x *= 0xd6e8feb86659fd93L;
224 | x ^= x >>> 32;
225 | return x;
226 | }
227 |
228 | public static long hash3(long x) {
229 | x ^= x >>> 45;
230 | x *= 0xc161abe5704b6c79L;
231 | x ^= x >>> 41;
232 | x *= 0xe3e5389aedbc90f7L;
233 | x ^= x >>> 56;
234 | x *= 0x1f9aba75a52db073L;
235 | x ^= x >>> 53;
236 | return x;
237 | }
238 |
239 | private IntegerUtil() {
240 | throw new RuntimeException();
241 | }
242 | }
243 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/common/util/SaveUtil.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.common.util;
2 |
3 | import ca.spottedleaf.starlight.common.chunk.ExtendedChunk;
4 | import ca.spottedleaf.starlight.common.light.SWMRNibbleArray;
5 | import ca.spottedleaf.starlight.common.light.StarLightEngine;
6 | import ca.spottedleaf.starlight.common.world.ExtendedSerializableChunkData;
7 | import com.mojang.logging.LogUtils;
8 | import net.minecraft.nbt.CompoundTag;
9 | import net.minecraft.nbt.ListTag;
10 | import net.minecraft.server.level.ServerLevel;
11 | import net.minecraft.world.level.ChunkPos;
12 | import net.minecraft.world.level.Level;
13 | import net.minecraft.world.level.LevelHeightAccessor;
14 | import net.minecraft.world.level.chunk.ChunkAccess;
15 | import net.minecraft.world.level.chunk.DataLayer;
16 | import net.minecraft.world.level.chunk.status.ChunkStatus;
17 | import net.minecraft.world.level.chunk.storage.SerializableChunkData;
18 | import org.slf4j.Logger;
19 |
20 | import java.util.Arrays;
21 | import java.util.List;
22 | import java.util.ListIterator;
23 |
24 | public final class SaveUtil {
25 |
26 | private static final Logger LOGGER = LogUtils.getLogger();
27 |
28 | private static final int STARLIGHT_LIGHT_VERSION = 9;
29 |
30 | public static int getLightVersion() {
31 | return STARLIGHT_LIGHT_VERSION;
32 | }
33 |
34 | private static final String BLOCKLIGHT_STATE_TAG = "starlight.blocklight_state";
35 | private static final String SKYLIGHT_STATE_TAG = "starlight.skylight_state";
36 | private static final String STARLIGHT_VERSION_TAG = "starlight.light_version";
37 |
38 | public static void prepareSaveLightHook(final ChunkAccess chunk, final SerializableChunkData data) {
39 | try {
40 | prepareSaveLightHookReal(chunk, data);
41 | } catch (final Throwable ex) {
42 | // failing to inject is not fatal so we catch anything here. if it fails, it will have correctly set lit to false
43 | // for Vanilla to relight on load and it will not set our lit tag so we will relight on load
44 | if (ex instanceof ThreadDeath) {
45 | throw (ThreadDeath)ex;
46 | }
47 | LOGGER.warn("Failed to inject light data into save data for chunk " + chunk.getPos() + ", chunk light will be recalculated on its next load", ex);
48 | }
49 | }
50 |
51 | private static void prepareSaveLightHookReal(final ChunkAccess chunk, final SerializableChunkData data) {
52 | // strip existing lighting data
53 | ListIterator iterator = data.sectionData().listIterator(); // mutable in vanilla
54 | while (iterator.hasNext()) {
55 | SerializableChunkData.SectionData sectionData = iterator.next();
56 | iterator.set(new SerializableChunkData.SectionData(sectionData.y(), sectionData.chunkSection(), null, null));
57 | }
58 |
59 | // store lighting data
60 | ((ExtendedSerializableChunkData) (Object) data).scalablelux$setBlockLight(
61 | Arrays.stream(((ExtendedChunk) chunk).getBlockNibbles())
62 | .map(SWMRNibbleArray::getSaveState)
63 | .toArray(SWMRNibbleArray.SaveState[]::new)
64 | );
65 | ((ExtendedSerializableChunkData) (Object) data).scalablelux$setSkyLight(
66 | Arrays.stream(((ExtendedChunk) chunk).getSkyNibbles())
67 | .map(SWMRNibbleArray::getSaveState)
68 | .toArray(SWMRNibbleArray.SaveState[]::new)
69 | );
70 | }
71 |
72 | public static void prepareSaveVanillaLightHook(final ServerLevel serverLevel, final ChunkAccess chunk, final SerializableChunkData data) {
73 | boolean lightCorrect = data.lightCorrect();
74 | ((ExtendedSerializableChunkData) (Object) data).scalablelux$setLightCorrect(false);
75 | try {
76 | prepareSaveVanillaLightHookReal(serverLevel, chunk, data);
77 | ((ExtendedSerializableChunkData) (Object) data).scalablelux$setLightCorrect(lightCorrect);
78 | } catch (final Throwable ex) {
79 | // failing to inject is not fatal so we catch anything here. if it fails, it will have correctly set lit to false
80 | // for Vanilla to relight on load and it will not set our lit tag so we will relight on load
81 | if (ex instanceof ThreadDeath) {
82 | throw (ThreadDeath)ex;
83 | }
84 | LOGGER.warn("Failed to inject light data into save data for chunk " + chunk.getPos() + ", chunk light will be recalculated on its next load", ex);
85 | }
86 | }
87 |
88 | private static void prepareSaveVanillaLightHookReal(final ServerLevel serverLevel, final ChunkAccess chunk, final SerializableChunkData data) {
89 | // replace existing lighting data
90 | SWMRNibbleArray.SaveState[] blockNibbles = Arrays.stream(((ExtendedChunk) chunk).getBlockNibbles())
91 | .map(SWMRNibbleArray::getSaveState)
92 | .toArray(SWMRNibbleArray.SaveState[]::new);
93 | SWMRNibbleArray.SaveState[] skyNibbles = Arrays.stream(((ExtendedChunk) chunk).getSkyNibbles())
94 | .map(SWMRNibbleArray::getSaveState)
95 | .toArray(SWMRNibbleArray.SaveState[]::new);
96 |
97 | ListIterator iterator = data.sectionData().listIterator(); // mutable in vanilla
98 | while (iterator.hasNext()) {
99 | SerializableChunkData.SectionData sectionData = iterator.next();
100 | int index = sectionData.y() - WorldUtil.getMinLightSection(serverLevel);
101 | byte[] blockRaw = blockNibbles[index] != null ? blockNibbles[index].data : null;
102 | byte[] skyRaw = skyNibbles[index] != null ? skyNibbles[index].data : null;
103 | iterator.set(new SerializableChunkData.SectionData(sectionData.y(), sectionData.chunkSection(), blockRaw != null ? new DataLayer(blockRaw) : new DataLayer(), skyRaw != null ? new DataLayer(skyRaw) : new DataLayer()));
104 | }
105 | }
106 |
107 | public static void saveLightHook(final SerializableChunkData data, final CompoundTag nbt) {
108 | try {
109 | saveLightHookReal(data, nbt);
110 | } catch (final Throwable ex) {
111 | // failing to inject is not fatal so we catch anything here. if it fails, it will have correctly set lit to false
112 | // for Vanilla to relight on load and it will not set our lit tag so we will relight on load
113 | if (ex instanceof ThreadDeath) {
114 | throw (ThreadDeath)ex;
115 | }
116 | LOGGER.warn("Failed to inject light data into save data for chunk " + data.chunkPos() + ", chunk light will be recalculated on its next load", ex);
117 | }
118 | }
119 |
120 | private static void saveLightHookReal(final SerializableChunkData data, final CompoundTag tag) {
121 | if (tag == null) {
122 | return;
123 | }
124 |
125 | // light sections are exclusive
126 | final int minSection = data.minSectionY() - 1; // exclusive
127 |
128 | SWMRNibbleArray.SaveState[] blockNibbles = ((ExtendedSerializableChunkData) (Object) data).scalablelux$getBlockLight();
129 | SWMRNibbleArray.SaveState[] skyNibbles = ((ExtendedSerializableChunkData) (Object) data).scalablelux$getSkyLight();
130 |
131 | final int maxSection = minSection + blockNibbles.length - 1; // exclusive
132 |
133 | boolean lit = data.lightCorrect();
134 | // diff start - store our tag for whether light data is init'd
135 | if (lit) {
136 | tag.putBoolean("isLightOn", false);
137 | }
138 | // diff end - store our tag for whether light data is init'd
139 | ChunkStatus status = ChunkStatus.byName(tag.getString("Status"));
140 |
141 | CompoundTag[] sections = new CompoundTag[maxSection - minSection + 1];
142 |
143 | ListTag sectionsStored = tag.getList("sections", 10);
144 |
145 | for (int i = 0; i < sectionsStored.size(); ++i) {
146 | CompoundTag sectionStored = sectionsStored.getCompound(i);
147 | int k = sectionStored.getByte("Y");
148 |
149 | // strip light data
150 | sectionStored.remove("BlockLight");
151 | sectionStored.remove("SkyLight");
152 |
153 | if (!sectionStored.isEmpty()) {
154 | sections[k - minSection] = sectionStored;
155 | }
156 | }
157 |
158 | if (lit && status.isOrAfter(ChunkStatus.LIGHT)) {
159 | for (int i = minSection; i <= maxSection; ++i) {
160 | SWMRNibbleArray.SaveState blockNibble = blockNibbles[i - minSection];
161 | SWMRNibbleArray.SaveState skyNibble = skyNibbles[i - minSection];
162 | if (blockNibble != null || skyNibble != null) {
163 | CompoundTag section = sections[i - minSection];
164 | if (section == null) {
165 | section = new CompoundTag();
166 | section.putByte("Y", (byte)i);
167 | sections[i - minSection] = section;
168 | }
169 |
170 | // we store under the same key so mod programs editing nbt
171 | // can still read the data, hopefully.
172 | // however, for compatibility we store chunks as unlit so vanilla
173 | // is forced to re-light them if it encounters our data. It's too much of a burden
174 | // to try and maintain compatibility with a broken and inferior skylight management system.
175 |
176 | if (blockNibble != null) {
177 | if (blockNibble.data != null) {
178 | section.putByteArray("BlockLight", blockNibble.data);
179 | }
180 | section.putInt(BLOCKLIGHT_STATE_TAG, blockNibble.state);
181 | }
182 |
183 | if (skyNibble != null) {
184 | if (skyNibble.data != null) {
185 | section.putByteArray("SkyLight", skyNibble.data);
186 | }
187 | section.putInt(SKYLIGHT_STATE_TAG, skyNibble.state);
188 | }
189 | }
190 | }
191 | }
192 |
193 | // rewrite section list
194 | sectionsStored.clear();
195 | for (CompoundTag section : sections) {
196 | if (section != null) {
197 | sectionsStored.add(section);
198 | }
199 | }
200 | tag.put("sections", sectionsStored);
201 | if (lit) {
202 | tag.putInt(STARLIGHT_VERSION_TAG, STARLIGHT_LIGHT_VERSION); // only mark as fully lit after we have successfully injected our data
203 | }
204 | }
205 |
206 | public static void prepareLoadLightHook(final LevelHeightAccessor levelHeightAccessor, final CompoundTag tag, final SerializableChunkData data) {
207 | try {
208 | prepareLoadLightHookReal(levelHeightAccessor, tag, data);
209 | } catch (final Throwable ex) {
210 | // failing to inject is not fatal so we catch anything here. if it fails, then we simply relight. Not a problem, we get correct
211 | // lighting in both cases.
212 | if (ex instanceof ThreadDeath) {
213 | throw (ThreadDeath)ex;
214 | }
215 | LOGGER.warn("Failed to load light for chunk " + data.chunkPos() + ", light will be recalculated", ex);
216 | }
217 | }
218 |
219 | private static void prepareLoadLightHookReal(final LevelHeightAccessor world, final CompoundTag tag, final SerializableChunkData data) {
220 | final int minSection = WorldUtil.getMinLightSection(world);
221 | final int maxSection = WorldUtil.getMaxLightSection(world);
222 |
223 | // mark as unlit in case we fail parsing
224 | ((ExtendedSerializableChunkData) (Object) data).scalablelux$setLightCorrect(false);
225 | ((ExtendedSerializableChunkData) (Object) data).scalableLux$setActuallyCorrect(false);
226 |
227 | SWMRNibbleArray.SaveState[] blockLight = StarLightEngine.getFilledEmptySaveState(world);
228 | SWMRNibbleArray.SaveState[] skyLight = StarLightEngine.getFilledEmptySaveState(world);
229 |
230 | boolean lit = tag.get("isLightOn") != null && tag.getInt(STARLIGHT_VERSION_TAG) == STARLIGHT_LIGHT_VERSION;
231 | // not enough context: assumes always reads skylight
232 | ChunkStatus status = data.chunkStatus();
233 |
234 | if (lit && status.isOrAfter(ChunkStatus.LIGHT)) {
235 | ListTag sections = tag.getList("sections", 10);
236 |
237 | for (int i = 0; i < sections.size(); ++i) {
238 | CompoundTag sectionData = sections.getCompound(i);
239 | int y = sectionData.getByte("Y");
240 |
241 | if (sectionData.contains("BlockLight", 7)) {
242 | blockLight[y - minSection] = new SWMRNibbleArray.SaveState(sectionData.getByteArray("BlockLight").clone(), sectionData.getInt(BLOCKLIGHT_STATE_TAG)); // clone for data safety
243 | } else {
244 | blockLight[y - minSection] = new SWMRNibbleArray.SaveState(null, sectionData.getInt(BLOCKLIGHT_STATE_TAG));
245 | }
246 |
247 | if (sectionData.contains("SkyLight", 7)) {
248 | // we store under the same key so mod programs editing nbt
249 | // can still read the data, hopefully.
250 | // however, for compatibility we store chunks as unlit so vanilla
251 | // is forced to re-light them if it encounters our data. It's too much of a burden
252 | // to try and maintain compatibility with a broken and inferior skylight management system.
253 | skyLight[y - minSection] = new SWMRNibbleArray.SaveState(sectionData.getByteArray("SkyLight").clone(), sectionData.getInt(SKYLIGHT_STATE_TAG)); // clone for data safety
254 | } else {
255 | skyLight[y - minSection] = new SWMRNibbleArray.SaveState(null, sectionData.getInt(SKYLIGHT_STATE_TAG));
256 | }
257 | }
258 | }
259 |
260 | ((ExtendedSerializableChunkData) (Object) data).scalablelux$setBlockLight(blockLight);
261 | ((ExtendedSerializableChunkData) (Object) data).scalablelux$setSkyLight(skyLight);
262 |
263 | ((ExtendedSerializableChunkData) (Object) data).scalableLux$setActuallyCorrect(lit);
264 | }
265 |
266 | public static void loadLightHook(final Level world, final SerializableChunkData data, final ChunkAccess into) {
267 | try {
268 | loadLightHookReal(world, data, into);
269 | } catch (final Throwable ex) {
270 | // failing to inject is not fatal so we catch anything here. if it fails, then we simply relight. Not a problem, we get correct
271 | // lighting in both cases.
272 | if (ex instanceof ThreadDeath) {
273 | throw (ThreadDeath)ex;
274 | }
275 | LOGGER.warn("Failed to load light for chunk " + data.chunkPos() + ", light will be recalculated", ex);
276 | }
277 | }
278 |
279 | private static void loadLightHookReal(final Level world, final SerializableChunkData data, final ChunkAccess into) {
280 | if (into == null) {
281 | return;
282 | }
283 | final int minSection = WorldUtil.getMinLightSection(world);
284 | final int maxSection = WorldUtil.getMaxLightSection(world);
285 |
286 | into.setLightCorrect(false); // mark as unlit in case we fail parsing
287 |
288 | SWMRNibbleArray[] blockNibbles = Arrays.stream(((ExtendedSerializableChunkData) (Object) data).scalablelux$getBlockLight())
289 | .map(state -> new SWMRNibbleArray(state.data, state.state))
290 | .toArray(SWMRNibbleArray[]::new);
291 | SWMRNibbleArray[] skyNibbles = Arrays.stream(((ExtendedSerializableChunkData) (Object) data).scalablelux$getSkyLight())
292 | .map(state -> new SWMRNibbleArray(state.data, state.state))
293 | .toArray(SWMRNibbleArray[]::new);
294 |
295 | boolean lit = ((ExtendedSerializableChunkData) (Object) data).scalablelux$getActuallyCorrect();
296 |
297 | ((ExtendedChunk)into).setBlockNibbles(blockNibbles);
298 | ((ExtendedChunk)into).setSkyNibbles(skyNibbles);
299 |
300 | into.setLightCorrect(lit); // now we set lit here, only after we've correctly parsed data
301 | }
302 |
303 | public static void loadVanillaLightHook(final Level world, final SerializableChunkData data, final ChunkAccess into) {
304 | try {
305 | loadVanillaLightHookReal(world, data, into);
306 | } catch (final Throwable ex) {
307 | // failing to inject is not fatal so we catch anything here. if it fails, then we simply relight. Not a problem, we get correct
308 | // lighting in both cases.
309 | if (ex instanceof ThreadDeath) {
310 | throw (ThreadDeath)ex;
311 | }
312 | LOGGER.warn("Failed to load light for chunk " + data.chunkPos() + ", light will be recalculated", ex);
313 | }
314 | }
315 |
316 | private static void loadVanillaLightHookReal(final Level world, final SerializableChunkData data, final ChunkAccess into) {
317 | if (into == null) {
318 | return;
319 | }
320 | final int minSection = WorldUtil.getMinLightSection(world);
321 | final int maxSection = WorldUtil.getMaxLightSection(world);
322 |
323 | ChunkStatus status = data.chunkStatus();
324 | boolean lit = into.isLightCorrect() && status.isOrAfter(ChunkStatus.LIGHT);
325 |
326 | into.setLightCorrect(false); // mark as unlit in case we fail parsing
327 |
328 | SWMRNibbleArray[] blockNibbles = StarLightEngine.getFilledEmptyLight(world);
329 | SWMRNibbleArray[] skyNibbles = StarLightEngine.getFilledEmptyLight(world);
330 |
331 | if (lit) {
332 | List sectionData = data.sectionData();
333 | for (int i = 0, sectionDataSize = sectionData.size(); i < sectionDataSize; i++) {
334 | SerializableChunkData.SectionData section = sectionData.get(i);
335 | int y = section.y();
336 |
337 | if (section.blockLight() != null) {
338 | // this is where our diff is
339 | blockNibbles[y - minSection] = SWMRNibbleArray.fromVanilla(section.blockLight()); // clone for data safety
340 | }
341 |
342 | if (section.skyLight() != null) {
343 | skyNibbles[y - minSection] = SWMRNibbleArray.fromVanilla(section.skyLight()); // clone for data safety
344 | }
345 | }
346 | // workaround vanilla quirk: skylight in sections below sections with initialized skylight is zero
347 | {
348 | boolean fillWithZero = false;
349 | for (int i = skyNibbles.length - 1; i >= 0; i--) {
350 | if (!skyNibbles[i].isNullNibbleVisible()) {
351 | fillWithZero = true;
352 | continue;
353 | }
354 | if (fillWithZero) {
355 | skyNibbles[i].setNonNull();
356 | skyNibbles[i].updateVisible();
357 | }
358 | }
359 | }
360 | }
361 |
362 | ((ExtendedChunk)into).setBlockNibbles(blockNibbles);
363 | ((ExtendedChunk)into).setSkyNibbles(skyNibbles);
364 |
365 | into.setLightCorrect(lit); // now we set lit here, only after we've correctly parsed data
366 | }
367 |
368 | private SaveUtil() {}
369 | }
370 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/common/util/WorldUtil.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.common.util;
2 |
3 | import net.minecraft.world.level.LevelHeightAccessor;
4 |
5 | public final class WorldUtil {
6 |
7 | // min, max are inclusive
8 |
9 | public static int getMaxSection(final LevelHeightAccessor world) {
10 | return world.getMaxSectionY(); // getMaxSection() is ~~exclusive~~ inclusive since 24w33a
11 | }
12 |
13 | public static int getMinSection(final LevelHeightAccessor world) {
14 | return world.getMinSectionY();
15 | }
16 |
17 | public static int getMaxLightSection(final LevelHeightAccessor world) {
18 | return getMaxSection(world) + 1;
19 | }
20 |
21 | public static int getMinLightSection(final LevelHeightAccessor world) {
22 | return getMinSection(world) - 1;
23 | }
24 |
25 |
26 |
27 | public static int getTotalSections(final LevelHeightAccessor world) {
28 | return getMaxSection(world) - getMinSection(world) + 1;
29 | }
30 |
31 | public static int getTotalLightSections(final LevelHeightAccessor world) {
32 | return getMaxLightSection(world) - getMinLightSection(world) + 1;
33 | }
34 |
35 | public static int getMinBlockY(final LevelHeightAccessor world) {
36 | return getMinSection(world) << 4;
37 | }
38 |
39 | public static int getMaxBlockY(final LevelHeightAccessor world) {
40 | return (getMaxSection(world) << 4) | 15;
41 | }
42 |
43 | private WorldUtil() {
44 | throw new RuntimeException();
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/common/world/ExtendedSerializableChunkData.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.common.world;
2 |
3 | import ca.spottedleaf.starlight.common.light.SWMRNibbleArray;
4 |
5 | public interface ExtendedSerializableChunkData {
6 |
7 | void scalablelux$setBlockLight(SWMRNibbleArray.SaveState[] light);
8 |
9 | void scalablelux$setSkyLight(SWMRNibbleArray.SaveState[] light);
10 |
11 | void scalableLux$setActuallyCorrect(boolean correct);
12 |
13 | SWMRNibbleArray.SaveState[] scalablelux$getBlockLight();
14 |
15 | SWMRNibbleArray.SaveState[] scalablelux$getSkyLight();
16 |
17 | boolean scalablelux$getActuallyCorrect();
18 |
19 | void scalablelux$setLightCorrect(boolean correct);
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/common/world/ExtendedWorld.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.common.world;
2 |
3 | import net.minecraft.world.level.chunk.ChunkAccess;
4 | import net.minecraft.world.level.chunk.LevelChunk;
5 |
6 | public interface ExtendedWorld {
7 |
8 | // rets full chunk without blocking
9 | public LevelChunk getChunkAtImmediately(final int chunkX, final int chunkZ);
10 |
11 | // rets chunk at any stage, if it exists, immediately
12 | public ChunkAccess getAnyChunkImmediately(final int chunkX, final int chunkZ);
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/mixin/client/multiplayer/ClientPacketListenerMixin.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.mixin.client.multiplayer;
2 |
3 | import ca.spottedleaf.starlight.common.light.StarLightLightingProvider;
4 | import net.minecraft.client.multiplayer.ClientLevel;
5 | import net.minecraft.client.multiplayer.ClientPacketListener;
6 | import net.minecraft.core.SectionPos;
7 | import net.minecraft.network.protocol.game.ClientGamePacketListener;
8 | import net.minecraft.network.protocol.game.ClientboundForgetLevelChunkPacket;
9 | import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
10 | import net.minecraft.network.protocol.game.ClientboundLightUpdatePacketData;
11 | import net.minecraft.world.level.ChunkPos;
12 | import net.minecraft.world.level.LightLayer;
13 | import net.minecraft.world.level.chunk.status.ChunkStatus;
14 | import net.minecraft.world.level.chunk.DataLayer;
15 | import net.minecraft.world.level.chunk.LevelChunk;
16 | import net.minecraft.world.level.lighting.LevelLightEngine;
17 | import org.jetbrains.annotations.Nullable;
18 | import org.spongepowered.asm.mixin.Mixin;
19 | import org.spongepowered.asm.mixin.Shadow;
20 | import org.spongepowered.asm.mixin.injection.At;
21 | import org.spongepowered.asm.mixin.injection.Inject;
22 | import org.spongepowered.asm.mixin.injection.Redirect;
23 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
24 |
25 | @Mixin(value = ClientPacketListener.class, priority = 1001)
26 | public abstract class ClientPacketListenerMixin implements ClientGamePacketListener {
27 |
28 | /*
29 | The call behaviors in the packet handler are much more clear about how they should affect the light engine,
30 | and as a result makes the client light load/unload more reliable
31 | */
32 |
33 | @Shadow
34 | private ClientLevel level;
35 |
36 | /*
37 | Now in 1.18 Mojang has added logic to delay rendering chunks until their lighting is ready (as they are delaying
38 | light updates). Fortunately for us, Starlight doesn't take any kind of hit loading in light data. So we have no reason
39 | to delay the light updates at all (and we shouldn't delay them or else desync might occur - such as with block updates).
40 | */
41 |
42 | @Shadow
43 | protected abstract void applyLightData(final int chunkX, final int chunkZ, final ClientboundLightUpdatePacketData clientboundLightUpdatePacketData, boolean markDirty);
44 |
45 | @Shadow
46 | protected abstract void enableChunkLight(final LevelChunk levelChunk, final int chunkX, final int chunkZ);
47 |
48 | /**
49 | * Call the runnable immediately to prevent desync
50 | * @author Spottedleaf
51 | */
52 | @Redirect(
53 | method = "handleLightUpdatePacket",
54 | at = @At(
55 | value = "INVOKE",
56 | target = "Lnet/minecraft/client/multiplayer/ClientLevel;queueLightUpdate(Ljava/lang/Runnable;)V"
57 | )
58 | )
59 | private void starlightCallUpdateImmediately(final ClientLevel instance, final Runnable runnable) {
60 | runnable.run();
61 | }
62 |
63 | /**
64 | * Re-route light update packet to our own logic
65 | * @author Spottedleaf
66 | */
67 | @Redirect(
68 | method = "readSectionList",
69 | at = @At(
70 | target = "Lnet/minecraft/world/level/lighting/LevelLightEngine;queueSectionData(Lnet/minecraft/world/level/LightLayer;Lnet/minecraft/core/SectionPos;Lnet/minecraft/world/level/chunk/DataLayer;)V",
71 | value = "INVOKE",
72 | ordinal = 0
73 | )
74 | )
75 | private void loadLightDataHook(final LevelLightEngine lightEngine, final LightLayer lightType, final SectionPos pos,
76 | final @Nullable DataLayer nibble) {
77 | ((StarLightLightingProvider)this.level.getChunkSource().getLightEngine()).clientUpdateLight(lightType, pos, nibble, true);
78 | }
79 |
80 |
81 | /**
82 | * Avoid calling Vanilla's logic here, and instead call our own.
83 | * @author Spottedleaf
84 | */
85 | @Redirect(
86 | method = "handleForgetLevelChunk",
87 | at = @At(
88 | value = "INVOKE",
89 | target = "Lnet/minecraft/client/multiplayer/ClientPacketListener;queueLightRemoval(Lnet/minecraft/network/protocol/game/ClientboundForgetLevelChunkPacket;)V"
90 | )
91 | )
92 | private void unloadLightDataHook(final ClientPacketListener instance, final ClientboundForgetLevelChunkPacket clientboundForgetLevelChunkPacket) {
93 | ((StarLightLightingProvider)this.level.getChunkSource().getLightEngine()).clientRemoveLightData(new ChunkPos(clientboundForgetLevelChunkPacket.pos().x, clientboundForgetLevelChunkPacket.pos().z));
94 | }
95 |
96 | /**
97 | * Don't call vanilla's load logic
98 | */
99 | @Redirect(
100 | method = "handleLevelChunkWithLight",
101 | at = @At(
102 | target = "Lnet/minecraft/client/multiplayer/ClientLevel;queueLightUpdate(Ljava/lang/Runnable;)V",
103 | value = "INVOKE",
104 | ordinal = 0
105 | )
106 | )
107 | private void postChunkLoadHookRedirect(final ClientLevel instance, final Runnable runnable) {
108 | // don't call vanilla's logic, see below
109 | }
110 |
111 | /**
112 | * Hook for loading in a chunk to the world
113 | * @author Spottedleaf
114 | */
115 | @Inject(
116 | method = "handleLevelChunkWithLight",
117 | at = @At(
118 | value = "RETURN"
119 | )
120 | )
121 | private void postChunkLoadHook(final ClientboundLevelChunkWithLightPacket clientboundLevelChunkWithLightPacket, final CallbackInfo ci) {
122 | final int chunkX = clientboundLevelChunkWithLightPacket.getX();
123 | final int chunkZ = clientboundLevelChunkWithLightPacket.getZ();
124 | final LevelChunk chunk = this.level.getChunkSource().getChunk(chunkX, chunkZ, ChunkStatus.FULL, false);
125 | if (chunk == null) {
126 | // failed to load
127 | return;
128 | }
129 | // load in light data from packet immediately
130 | this.applyLightData(chunkX, chunkZ, clientboundLevelChunkWithLightPacket.getLightData(), true);
131 | ((StarLightLightingProvider)this.level.getChunkSource().getLightEngine()).clientChunkLoad(new ChunkPos(chunkX, chunkZ), chunk);
132 |
133 | // we need this for the update chunk status call, so that it can tell starlight what sections are empty and such
134 | this.enableChunkLight(chunk, chunkX, chunkZ);
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/mixin/client/world/ClientLevelMixin.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.mixin.client.world;
2 |
3 | import ca.spottedleaf.starlight.common.world.ExtendedWorld;
4 | import net.minecraft.client.multiplayer.ClientChunkCache;
5 | import net.minecraft.client.multiplayer.ClientLevel;
6 | import net.minecraft.core.Holder;
7 | import net.minecraft.core.RegistryAccess;
8 | import net.minecraft.resources.ResourceKey;
9 | import net.minecraft.util.profiling.ProfilerFiller;
10 | import net.minecraft.world.level.Level;
11 | import net.minecraft.world.level.chunk.ChunkAccess;
12 | import net.minecraft.world.level.chunk.LevelChunk;
13 | import net.minecraft.world.level.dimension.DimensionType;
14 | import net.minecraft.world.level.storage.WritableLevelData;
15 | import org.spongepowered.asm.mixin.Mixin;
16 | import org.spongepowered.asm.mixin.Shadow;
17 | import java.util.function.Supplier;
18 |
19 | @Mixin(ClientLevel.class)
20 | public abstract class ClientLevelMixin extends Level implements ExtendedWorld {
21 |
22 | protected ClientLevelMixin(WritableLevelData writableLevelData, ResourceKey resourceKey, RegistryAccess registryAccess, Holder holder, boolean bl, boolean bl2, long l, int i) {
23 | super(writableLevelData, resourceKey, registryAccess, holder, bl, bl2, l, i);
24 | }
25 |
26 | @Shadow
27 | public abstract ClientChunkCache getChunkSource();
28 |
29 | @Override
30 | public final LevelChunk getChunkAtImmediately(final int chunkX, final int chunkZ) {
31 | return this.getChunkSource().getChunk(chunkX, chunkZ, false);
32 | }
33 |
34 | @Override
35 | public final ChunkAccess getAnyChunkImmediately(int chunkX, int chunkZ) {
36 | return this.getChunkSource().getChunk(chunkX, chunkZ, false);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/mixin/common/blockstate/BlockStateBaseMixin.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.mixin.common.blockstate;
2 |
3 | import ca.spottedleaf.starlight.common.blockstate.ExtendedAbstractBlockState;
4 | import com.google.common.collect.ImmutableMap;
5 | import com.mojang.serialization.MapCodec;
6 | import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap;
7 | import net.minecraft.world.level.block.Block;
8 | import net.minecraft.world.level.block.state.BlockBehaviour;
9 | import net.minecraft.world.level.block.state.BlockState;
10 | import net.minecraft.world.level.block.state.StateHolder;
11 | import net.minecraft.world.level.block.state.properties.Property;
12 | import org.spongepowered.asm.mixin.Final;
13 | import org.spongepowered.asm.mixin.Mixin;
14 | import org.spongepowered.asm.mixin.Shadow;
15 | import org.spongepowered.asm.mixin.Unique;
16 | import org.spongepowered.asm.mixin.injection.At;
17 | import org.spongepowered.asm.mixin.injection.Inject;
18 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
19 |
20 | @Mixin(BlockBehaviour.BlockStateBase.class)
21 | public abstract class BlockStateBaseMixin extends StateHolder implements ExtendedAbstractBlockState {
22 |
23 | @Shadow
24 | @Final
25 | private boolean useShapeForLightOcclusion;
26 |
27 | @Shadow
28 | @Final
29 | private boolean canOcclude;
30 |
31 | @Unique
32 | private boolean isConditionallyFullOpaque;
33 |
34 | protected BlockStateBaseMixin(Block object, Reference2ObjectArrayMap, Comparable>> reference2ObjectArrayMap, MapCodec mapCodec) {
35 | super(object, reference2ObjectArrayMap, mapCodec);
36 | }
37 |
38 | /**
39 | * Initialises our light state for this block.
40 | */
41 | @Inject(
42 | method = "initCache",
43 | at = @At("RETURN")
44 | )
45 | public void initLightAccessState(final CallbackInfo ci) {
46 | this.isConditionallyFullOpaque = this.canOcclude & this.useShapeForLightOcclusion;
47 | }
48 |
49 | @Override
50 | public final boolean isConditionallyFullOpaque() {
51 | return this.isConditionallyFullOpaque;
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/ChunkAccessMixin.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.mixin.common.chunk;
2 |
3 | import ca.spottedleaf.starlight.common.chunk.ExtendedChunk;
4 | import ca.spottedleaf.starlight.common.light.SWMRNibbleArray;
5 | import ca.spottedleaf.starlight.common.light.StarLightEngine;
6 | import net.minecraft.core.Registry;
7 | import net.minecraft.world.level.ChunkPos;
8 | import net.minecraft.world.level.LevelHeightAccessor;
9 | import net.minecraft.world.level.chunk.ChunkAccess;
10 | import net.minecraft.world.level.chunk.ImposterProtoChunk;
11 | import net.minecraft.world.level.chunk.LevelChunkSection;
12 | import net.minecraft.world.level.chunk.UpgradeData;
13 | import net.minecraft.world.level.levelgen.blending.BlendingData;
14 | import net.minecraft.world.level.lighting.ChunkSkyLightSources;
15 | import org.spongepowered.asm.mixin.Mixin;
16 | import org.spongepowered.asm.mixin.Shadow;
17 | import org.spongepowered.asm.mixin.Unique;
18 | import org.spongepowered.asm.mixin.injection.At;
19 | import org.spongepowered.asm.mixin.injection.Inject;
20 | import org.spongepowered.asm.mixin.injection.Redirect;
21 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
22 |
23 | @Mixin(ChunkAccess.class)
24 | public abstract class ChunkAccessMixin implements ExtendedChunk {
25 |
26 | @Shadow
27 | protected ChunkSkyLightSources skyLightSources;
28 |
29 |
30 | @Unique
31 | private volatile SWMRNibbleArray[] blockNibbles;
32 |
33 | @Unique
34 | private volatile SWMRNibbleArray[] skyNibbles;
35 |
36 | @Unique
37 | private volatile boolean[] skyEmptinessMap;
38 |
39 | @Unique
40 | private volatile boolean[] blockEmptinessMap;
41 |
42 | @Override
43 | public SWMRNibbleArray[] getBlockNibbles() {
44 | return this.blockNibbles;
45 | }
46 |
47 | @Override
48 | public void setBlockNibbles(final SWMRNibbleArray[] nibbles) {
49 | this.blockNibbles = nibbles;
50 | }
51 |
52 | @Override
53 | public SWMRNibbleArray[] getSkyNibbles() {
54 | return this.skyNibbles;
55 | }
56 |
57 | @Override
58 | public void setSkyNibbles(final SWMRNibbleArray[] nibbles) {
59 | this.skyNibbles = nibbles;
60 | }
61 |
62 | @Override
63 | public boolean[] getSkyEmptinessMap() {
64 | return this.skyEmptinessMap;
65 | }
66 |
67 | @Override
68 | public void setSkyEmptinessMap(final boolean[] emptinessMap) {
69 | this.skyEmptinessMap = emptinessMap;
70 | }
71 |
72 | @Override
73 | public boolean[] getBlockEmptinessMap() {
74 | return this.blockEmptinessMap;
75 | }
76 |
77 | @Override
78 | public void setBlockEmptinessMap(final boolean[] emptinessMap) {
79 | this.blockEmptinessMap = emptinessMap;
80 | }
81 |
82 | /**
83 | * @reason Remove unused skylight sources, and initialise nibble arrays.
84 | * @author Spottedleaf
85 | */
86 | @Inject(
87 | method = "",
88 | at = @At(
89 | value = "RETURN"
90 | )
91 | )
92 | private void nullSources(ChunkPos chunkPos, UpgradeData upgradeData, LevelHeightAccessor levelHeightAccessor,
93 | Registry registry, long l, LevelChunkSection[] levelChunkSections, BlendingData blendingData,
94 | CallbackInfo ci) {
95 | this.skyLightSources = null;
96 | if (!((Object)this instanceof ImposterProtoChunk)) {
97 | this.setBlockNibbles(StarLightEngine.getFilledEmptyLight(levelHeightAccessor));
98 | this.setSkyNibbles(StarLightEngine.getFilledEmptyLight(levelHeightAccessor));
99 | }
100 | }
101 |
102 | /**
103 | * @reason Remove unused skylight sources
104 | * @author Spottedleaf
105 | */
106 | @Redirect(
107 | method = "initializeLightSources",
108 | at = @At(
109 | value = "INVOKE",
110 | target = "Lnet/minecraft/world/level/lighting/ChunkSkyLightSources;fillFrom(Lnet/minecraft/world/level/chunk/ChunkAccess;)V"
111 | )
112 | )
113 | private void skipInit(final ChunkSkyLightSources instance, final ChunkAccess chunkAccess) {}
114 | }
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/EmptyLevelChunkMixin.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.mixin.common.chunk;
2 |
3 | import ca.spottedleaf.starlight.common.chunk.ExtendedChunk;
4 | import ca.spottedleaf.starlight.common.light.SWMRNibbleArray;
5 | import ca.spottedleaf.starlight.common.light.StarLightEngine;
6 | import net.minecraft.world.level.ChunkPos;
7 | import net.minecraft.world.level.Level;
8 | import net.minecraft.world.level.chunk.EmptyLevelChunk;
9 | import net.minecraft.world.level.chunk.LevelChunk;
10 | import org.spongepowered.asm.mixin.Mixin;
11 |
12 | @Mixin(EmptyLevelChunk.class)
13 | public abstract class EmptyLevelChunkMixin extends LevelChunk implements ExtendedChunk {
14 |
15 | public EmptyLevelChunkMixin(final Level level, final ChunkPos pos) {
16 | super(level, pos);
17 | }
18 |
19 | @Override
20 | public SWMRNibbleArray[] getBlockNibbles() {
21 | return StarLightEngine.getFilledEmptyLight(this.getLevel());
22 | }
23 |
24 | @Override
25 | public void setBlockNibbles(final SWMRNibbleArray[] nibbles) {}
26 |
27 | @Override
28 | public SWMRNibbleArray[] getSkyNibbles() {
29 | return StarLightEngine.getFilledEmptyLight(this.getLevel());
30 | }
31 |
32 | @Override
33 | public void setSkyNibbles(final SWMRNibbleArray[] nibbles) {}
34 |
35 | @Override
36 | public boolean[] getSkyEmptinessMap() {
37 | return null;
38 | }
39 |
40 | @Override
41 | public void setSkyEmptinessMap(final boolean[] emptinessMap) {}
42 |
43 | @Override
44 | public boolean[] getBlockEmptinessMap() {
45 | return null;
46 | }
47 |
48 | @Override
49 | public void setBlockEmptinessMap(final boolean[] emptinessMap) {}
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/ImposterProtoChunkMixin.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.mixin.common.chunk;
2 |
3 | import ca.spottedleaf.starlight.common.light.SWMRNibbleArray;
4 | import ca.spottedleaf.starlight.common.chunk.ExtendedChunk;
5 | import net.minecraft.core.Registry;
6 | import net.minecraft.core.registries.Registries;
7 | import net.minecraft.world.level.chunk.ImposterProtoChunk;
8 | import net.minecraft.world.level.chunk.LevelChunk;
9 | import net.minecraft.world.level.chunk.ProtoChunk;
10 | import net.minecraft.world.level.chunk.UpgradeData;
11 | import org.spongepowered.asm.mixin.Final;
12 | import org.spongepowered.asm.mixin.Mixin;
13 | import org.spongepowered.asm.mixin.Shadow;
14 |
15 | @Mixin(ImposterProtoChunk.class)
16 | public abstract class ImposterProtoChunkMixin extends ProtoChunk implements ExtendedChunk {
17 |
18 | @Final
19 | @Shadow
20 | private LevelChunk wrapped;
21 |
22 | public ImposterProtoChunkMixin(final LevelChunk levelChunk, final boolean bl) {
23 | super(levelChunk.getPos(), UpgradeData.EMPTY, levelChunk, levelChunk.getLevel().registryAccess().lookupOrThrow(Registries.BIOME), levelChunk.getBlendingData());
24 | }
25 |
26 | @Override
27 | public SWMRNibbleArray[] getBlockNibbles() {
28 | return ((ExtendedChunk)this.wrapped).getBlockNibbles();
29 | }
30 |
31 | @Override
32 | public void setBlockNibbles(final SWMRNibbleArray[] nibbles) {
33 | ((ExtendedChunk)this.wrapped).setBlockNibbles(nibbles);
34 | }
35 |
36 | @Override
37 | public SWMRNibbleArray[] getSkyNibbles() {
38 | return ((ExtendedChunk)this.wrapped).getSkyNibbles();
39 | }
40 |
41 | @Override
42 | public void setSkyNibbles(final SWMRNibbleArray[] nibbles) {
43 | ((ExtendedChunk)this.wrapped).setSkyNibbles(nibbles);
44 | }
45 |
46 | @Override
47 | public boolean[] getSkyEmptinessMap() {
48 | return ((ExtendedChunk)this.wrapped).getSkyEmptinessMap();
49 | }
50 |
51 | @Override
52 | public void setSkyEmptinessMap(final boolean[] emptinessMap) {
53 | ((ExtendedChunk)this.wrapped).setSkyEmptinessMap(emptinessMap);
54 | }
55 |
56 | @Override
57 | public boolean[] getBlockEmptinessMap() {
58 | return ((ExtendedChunk)this.wrapped).getBlockEmptinessMap();
59 | }
60 |
61 | @Override
62 | public void setBlockEmptinessMap(final boolean[] emptinessMap) {
63 | ((ExtendedChunk)this.wrapped).setBlockEmptinessMap(emptinessMap);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/LevelChunkMixin.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.mixin.common.chunk;
2 |
3 | import ca.spottedleaf.starlight.common.chunk.ExtendedChunk;
4 | import net.minecraft.server.level.ServerLevel;
5 | import net.minecraft.world.level.BlockGetter;
6 | import net.minecraft.world.level.chunk.LevelChunk;
7 | import net.minecraft.world.level.chunk.ProtoChunk;
8 | import net.minecraft.world.level.lighting.ChunkSkyLightSources;
9 | import org.spongepowered.asm.mixin.Mixin;
10 | import org.spongepowered.asm.mixin.injection.At;
11 | import org.spongepowered.asm.mixin.injection.Inject;
12 | import org.spongepowered.asm.mixin.injection.Redirect;
13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
14 |
15 | @Mixin(LevelChunk.class)
16 | public abstract class LevelChunkMixin implements ExtendedChunk {
17 |
18 | /**
19 | * Copies the nibble data from the protochunk.
20 | * TODO since this is a constructor inject, check for new constructors on update.
21 | */
22 | @Inject(
23 | method = "(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/level/chunk/ProtoChunk;Lnet/minecraft/world/level/chunk/LevelChunk$PostLoadProcessor;)V",
24 | at = @At("TAIL")
25 | )
26 | public void onTransitionToFull(ServerLevel serverLevel, ProtoChunk protoChunk, LevelChunk.PostLoadProcessor postLoadProcessor, CallbackInfo ci) {
27 | this.setBlockNibbles(((ExtendedChunk)protoChunk).getBlockNibbles());
28 | this.setSkyNibbles(((ExtendedChunk)protoChunk).getSkyNibbles());
29 | this.setSkyEmptinessMap(((ExtendedChunk)protoChunk).getSkyEmptinessMap());
30 | this.setBlockEmptinessMap(((ExtendedChunk)protoChunk).getBlockEmptinessMap());
31 | }
32 |
33 | /**
34 | * @reason Remove unused skylight sources
35 | * @author Spottedleaf
36 | */
37 | @Redirect(
38 | method = "setBlockState",
39 | at = @At(
40 | value = "INVOKE",
41 | target = "Lnet/minecraft/world/level/lighting/ChunkSkyLightSources;update(Lnet/minecraft/world/level/BlockGetter;III)Z"
42 | )
43 | )
44 | private boolean skipLightSources(final ChunkSkyLightSources instance, final BlockGetter blockGetter,
45 | final int x, final int y, final int z) {
46 | return false;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/mixin/common/chunk/ProtoChunkMixin.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.mixin.common.chunk;
2 |
3 | import ca.spottedleaf.starlight.common.chunk.ExtendedChunk;
4 | import net.minecraft.world.level.BlockGetter;
5 | import net.minecraft.world.level.chunk.ProtoChunk;
6 | import net.minecraft.world.level.lighting.ChunkSkyLightSources;
7 | import org.spongepowered.asm.mixin.Mixin;
8 | import org.spongepowered.asm.mixin.injection.At;
9 | import org.spongepowered.asm.mixin.injection.Redirect;
10 |
11 | @Mixin(ProtoChunk.class)
12 | public abstract class ProtoChunkMixin implements ExtendedChunk {
13 |
14 | /**
15 | * @reason Remove unused skylight sources
16 | * @author Spottedleaf
17 | */
18 | @Redirect(
19 | method = "setBlockState",
20 | at = @At(
21 | value = "INVOKE",
22 | target = "Lnet/minecraft/world/level/lighting/ChunkSkyLightSources;update(Lnet/minecraft/world/level/BlockGetter;III)Z"
23 | )
24 | )
25 | private boolean skipLightSources(final ChunkSkyLightSources instance, final BlockGetter blockGetter,
26 | final int x, final int y, final int z) {
27 | return false;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/mixin/common/lightengine/LevelLightEngineMixin.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.mixin.common.lightengine;
2 |
3 | import ca.spottedleaf.starlight.common.chunk.ExtendedChunk;
4 | import ca.spottedleaf.starlight.common.light.SWMRNibbleArray;
5 | import ca.spottedleaf.starlight.common.light.StarLightEngine;
6 | import ca.spottedleaf.starlight.common.light.StarLightInterface;
7 | import ca.spottedleaf.starlight.common.light.StarLightLightingProvider;
8 | import ca.spottedleaf.starlight.common.util.CoordinateUtils;
9 | import ca.spottedleaf.starlight.common.util.WorldUtil;
10 | import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
11 | import net.minecraft.core.BlockPos;
12 | import net.minecraft.core.SectionPos;
13 | import net.minecraft.world.level.ChunkPos;
14 | import net.minecraft.world.level.Level;
15 | import net.minecraft.world.level.LightLayer;
16 | import net.minecraft.world.level.chunk.ChunkAccess;
17 | import net.minecraft.world.level.chunk.DataLayer;
18 | import net.minecraft.world.level.chunk.LevelChunk;
19 | import net.minecraft.world.level.chunk.LightChunkGetter;
20 | import net.minecraft.world.level.lighting.LayerLightEventListener;
21 | import net.minecraft.world.level.lighting.LevelLightEngine;
22 | import net.minecraft.world.level.lighting.LightEngine;
23 | import net.minecraft.world.level.lighting.LightEventListener;
24 | import org.jetbrains.annotations.Nullable;
25 | import org.spongepowered.asm.mixin.Mixin;
26 | import org.spongepowered.asm.mixin.Overwrite;
27 | import org.spongepowered.asm.mixin.Shadow;
28 | import org.spongepowered.asm.mixin.Unique;
29 | import org.spongepowered.asm.mixin.injection.At;
30 | import org.spongepowered.asm.mixin.injection.Inject;
31 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
32 |
33 | @Mixin(LevelLightEngine.class)
34 | public abstract class LevelLightEngineMixin implements LightEventListener, StarLightLightingProvider {
35 |
36 | @Shadow
37 | @Nullable
38 | private LightEngine, ?> blockEngine;
39 |
40 | @Shadow
41 | @Nullable
42 | private LightEngine, ?> skyEngine;
43 |
44 | @Unique
45 | protected StarLightInterface lightEngine;
46 |
47 | @Override
48 | public final StarLightInterface getLightEngine() {
49 | return this.lightEngine;
50 | }
51 |
52 | /**
53 | *
54 | * TODO since this is a constructor inject, check on update for new constructors
55 | */
56 | @Inject(
57 | method = "", at = @At("TAIL")
58 | )
59 | public void construct(final LightChunkGetter chunkProvider, final boolean hasBlockLight, final boolean hasSkyLight,
60 | final CallbackInfo ci) {
61 | // avoid ClassCastException in cases where custom LightChunkGetters do not return a Level from getLevel()
62 | if (chunkProvider.getLevel() instanceof Level) {
63 | this.lightEngine = new StarLightInterface(chunkProvider, hasSkyLight, hasBlockLight, (LevelLightEngine)(Object)this);
64 | } else {
65 | this.lightEngine = new StarLightInterface(null, hasSkyLight, hasBlockLight, (LevelLightEngine)(Object)this);
66 | }
67 | // intentionally destroy mods hooking into old light engine state
68 | this.blockEngine = null;
69 | this.skyEngine = null;
70 | }
71 |
72 | /**
73 | * @reason Route to new light engine
74 | * @author Spottedleaf
75 | */
76 | @Overwrite
77 | public void checkBlock(final BlockPos pos) {
78 | this.lightEngine.blockChange(pos.immutable());
79 | }
80 |
81 | /**
82 | * @reason Route to new light engine
83 | * @author Spottedleaf
84 | */
85 | @Overwrite
86 | public boolean hasLightWork() {
87 | // route to new light engine
88 | return this.lightEngine.hasUpdates();
89 | }
90 |
91 | /**
92 | * @reason Hook into new light engine for light updates
93 | * @author Spottedleaf
94 | */
95 | @Overwrite
96 | public int runLightUpdates() {
97 | // replace impl
98 | final boolean hadUpdates = this.hasLightWork();
99 | this.lightEngine.propagateChanges();
100 | return hadUpdates ? 1 : 0;
101 | }
102 |
103 | /**
104 | * @reason New light engine hook for handling empty section changes
105 | * @author Spottedleaf
106 | */
107 | @Overwrite
108 | public void updateSectionStatus(final SectionPos pos, final boolean notReady) {
109 | this.lightEngine.sectionChange(pos, notReady);
110 | }
111 |
112 | /**
113 | * @reason Avoid messing with the vanilla light engine state
114 | * @author Spottedleaf
115 | */
116 | @Overwrite
117 | public void setLightEnabled(final ChunkPos pos, final boolean lightEnabled) {
118 | // not invoked by the client
119 | }
120 |
121 | /**
122 | * @reason Avoid messing with the vanilla light engine state
123 | * @author Spottedleaf
124 | */
125 | @Overwrite
126 | public void propagateLightSources(ChunkPos param0) {
127 | // not invoked by the client
128 | }
129 |
130 | /**
131 | * @reason Replace light views with our own that hook into the new light engine instead of vanilla's
132 | * @author Spottedleaf
133 | */
134 | @Overwrite
135 | public LayerLightEventListener getLayerListener(final LightLayer lightType) {
136 | return lightType == LightLayer.BLOCK ? this.lightEngine.getBlockReader() : this.lightEngine.getSkyReader();
137 | }
138 |
139 | /**
140 | * @reason Avoid messing with the vanilla light engine state
141 | * @author Spottedleaf
142 | */
143 | @Overwrite
144 | public void queueSectionData(final LightLayer lightType, final SectionPos pos, @Nullable final DataLayer nibble) {
145 | // do not allow modification of data from the non-chunk load hooks
146 | }
147 |
148 | /**
149 | * @reason Avoid messing with the vanilla light engine state
150 | * @author Spottedleaf
151 | */
152 | @Overwrite
153 | public String getDebugData(final LightLayer lightType, final SectionPos pos) {
154 | // TODO would be nice to make use of this
155 | return "n/a";
156 | }
157 |
158 | /**
159 | * @reason Avoid messing with the vanilla light engine state
160 | * @author Spottedleaf
161 | */
162 | @Overwrite
163 | public void retainData(final ChunkPos pos, final boolean retainData) {
164 | // not used by new light impl
165 | }
166 |
167 | /**
168 | * @reason Need to use our own hooks for retrieving light data
169 | * @author Spottedleaf
170 | */
171 | @Overwrite
172 | public int getRawBrightness(final BlockPos pos, final int ambientDarkness) {
173 | // need to use new light hooks for this
174 | return this.lightEngine.getRawBrightness(pos, ambientDarkness);
175 | }
176 |
177 | /**
178 | * @reason Need to use our own hooks for checking this state
179 | * @author Spottedleaf
180 | */
181 | @Overwrite
182 | public boolean lightOnInColumn(final long pos) {
183 | final long key = CoordinateUtils.getChunkKey(SectionPos.x(pos), SectionPos.z(pos));
184 | return (!this.lightEngine.hasBlockLight() || this.blockLightMap.get(key) != null) && (!this.lightEngine.hasSkyLight() || this.skyLightMap.get(key) != null);
185 | }
186 |
187 | @Unique
188 | protected final Long2ObjectOpenHashMap blockLightMap = new Long2ObjectOpenHashMap<>();
189 |
190 | @Unique
191 | protected final Long2ObjectOpenHashMap skyLightMap = new Long2ObjectOpenHashMap<>();
192 |
193 | @Override
194 | public void clientUpdateLight(final LightLayer lightType, final SectionPos pos,
195 | final DataLayer nibble, final boolean trustEdges) {
196 | if (((Object)this).getClass() != LevelLightEngine.class) {
197 | throw new IllegalStateException("This hook is for the CLIENT ONLY");
198 | }
199 | // data storage changed with new light impl
200 | final ChunkAccess chunk = this.getLightEngine().getAnyChunkNow(pos.getX(), pos.getZ());
201 | switch (lightType) {
202 | case BLOCK: {
203 | final SWMRNibbleArray[] blockNibbles = this.blockLightMap.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> {
204 | return StarLightEngine.getFilledEmptyLight(this.lightEngine.getWorld());
205 | });
206 |
207 | blockNibbles[pos.getY() - WorldUtil.getMinLightSection(this.lightEngine.getWorld())] = SWMRNibbleArray.fromVanilla(nibble);
208 |
209 | if (chunk != null) {
210 | ((ExtendedChunk)chunk).setBlockNibbles(blockNibbles);
211 | this.lightEngine.getLightAccess().onLightUpdate(LightLayer.BLOCK, pos);
212 | }
213 | break;
214 | }
215 | case SKY: {
216 | final SWMRNibbleArray[] skyNibbles = this.skyLightMap.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> {
217 | return StarLightEngine.getFilledEmptyLight(this.lightEngine.getWorld());
218 | });
219 |
220 | skyNibbles[pos.getY() - WorldUtil.getMinLightSection(this.lightEngine.getWorld())] = SWMRNibbleArray.fromVanilla(nibble);
221 |
222 | if (chunk != null) {
223 | ((ExtendedChunk)chunk).setSkyNibbles(skyNibbles);
224 | this.lightEngine.getLightAccess().onLightUpdate(LightLayer.SKY, pos);
225 | }
226 | break;
227 | }
228 | }
229 | }
230 |
231 | @Override
232 | public void clientRemoveLightData(final ChunkPos chunkPos) {
233 | if (((Object)this).getClass() != LevelLightEngine.class) {
234 | throw new IllegalStateException("This hook is for the CLIENT ONLY");
235 | }
236 | this.blockLightMap.remove(CoordinateUtils.getChunkKey(chunkPos));
237 | this.skyLightMap.remove(CoordinateUtils.getChunkKey(chunkPos));
238 | }
239 |
240 | @Override
241 | public void clientChunkLoad(final ChunkPos pos, final LevelChunk chunk) {
242 | if (((Object)this).getClass() != LevelLightEngine.class) {
243 | throw new IllegalStateException("This hook is for the CLIENT ONLY");
244 | }
245 | final long key = CoordinateUtils.getChunkKey(pos);
246 | final SWMRNibbleArray[] blockNibbles = this.blockLightMap.get(key);
247 | final SWMRNibbleArray[] skyNibbles = this.skyLightMap.get(key);
248 | if (blockNibbles != null) {
249 | ((ExtendedChunk)chunk).setBlockNibbles(blockNibbles);
250 | }
251 | if (skyNibbles != null) {
252 | ((ExtendedChunk)chunk).setSkyNibbles(skyNibbles);
253 | }
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/mixin/common/lightengine/ThreadedLevelLightEngineMixin.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.mixin.common.lightengine;
2 |
3 | import ca.spottedleaf.starlight.common.light.StarLightEngine;
4 | import ca.spottedleaf.starlight.common.light.StarLightInterface;
5 | import ca.spottedleaf.starlight.common.light.StarLightLightingProvider;
6 | import ca.spottedleaf.starlight.common.thread.GlobalExecutors;
7 | import ca.spottedleaf.starlight.common.util.CoordinateUtils;
8 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
9 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
10 | import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
11 | import net.minecraft.core.BlockPos;
12 | import net.minecraft.core.SectionPos;
13 | import net.minecraft.server.level.ChunkMap;
14 | import net.minecraft.server.level.ServerLevel;
15 | import net.minecraft.server.level.ThreadedLevelLightEngine;
16 | import net.minecraft.world.level.ChunkPos;
17 | import net.minecraft.world.level.LightLayer;
18 | import net.minecraft.world.level.chunk.ChunkAccess;
19 | import net.minecraft.world.level.chunk.status.ChunkStatus;
20 | import net.minecraft.world.level.chunk.DataLayer;
21 | import net.minecraft.world.level.chunk.LightChunkGetter;
22 | import net.minecraft.world.level.lighting.LevelLightEngine;
23 | import org.jetbrains.annotations.Nullable;
24 | import org.slf4j.Logger;
25 | import org.spongepowered.asm.mixin.Final;
26 | import org.spongepowered.asm.mixin.Mixin;
27 | import org.spongepowered.asm.mixin.Overwrite;
28 | import org.spongepowered.asm.mixin.Shadow;
29 | import org.spongepowered.asm.mixin.Unique;
30 | import org.spongepowered.asm.mixin.injection.At;
31 |
32 | import java.util.concurrent.CompletableFuture;
33 | import java.util.concurrent.atomic.AtomicLong;
34 | import java.util.function.Supplier;
35 |
36 | @Mixin(ThreadedLevelLightEngine.class)
37 | public abstract class ThreadedLevelLightEngineMixin extends LevelLightEngine implements StarLightLightingProvider {
38 |
39 | @Final
40 | @Shadow
41 | private ChunkMap chunkMap;
42 |
43 | @Final
44 | @Shadow
45 | private static Logger LOGGER;
46 |
47 | @Shadow
48 | public abstract void tryScheduleUpdate();
49 |
50 | public ThreadedLevelLightEngineMixin(final LightChunkGetter chunkProvider, final boolean hasBlockLight, final boolean hasSkyLight) {
51 | super(chunkProvider, hasBlockLight, hasSkyLight);
52 | }
53 |
54 | @Unique
55 | private final Long2IntOpenHashMap chunksBeingWorkedOn = new Long2IntOpenHashMap();
56 |
57 | @Unique
58 | private void queueTaskForSection(final int chunkX, final int chunkY, final int chunkZ,
59 | final Supplier runnable) {
60 | final ServerLevel world = (ServerLevel)this.getLightEngine().getWorld();
61 |
62 | final ChunkAccess center = this.getLightEngine().getAnyChunkNow(chunkX, chunkZ);
63 | if (center == null || !center.getPersistedStatus().isOrAfter(ChunkStatus.LIGHT)) {
64 | // do not accept updates in unlit chunks, unless we might be generating a chunk. thanks to the amazing
65 | // chunk scheduling, we could be lighting and generating a chunk at the same time
66 | return;
67 | }
68 |
69 | if (center.getPersistedStatus() != ChunkStatus.FULL) { // TODO check if getHighestGeneratedStatus() is a better idea
70 | // do not keep chunk loaded, we are probably in a gen thread
71 | // if we proceed to add a ticket the chunk will be loaded, which is not what we want (avoid cascading gen)
72 | runnable.get();
73 | return;
74 | }
75 |
76 | if (!world.getChunkSource().chunkMap.mainThreadExecutor.isSameThread()) {
77 | // ticket logic is not safe to run off-main, re-schedule
78 | world.getChunkSource().chunkMap.mainThreadExecutor.execute(() -> {
79 | this.queueTaskForSection(chunkX, chunkY, chunkZ, runnable);
80 | });
81 | return;
82 | }
83 |
84 | final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ);
85 |
86 | final StarLightInterface.LightQueue.ChunkTasks updateFuture = runnable.get();
87 |
88 | if (updateFuture == null) {
89 | // not scheduled
90 | return;
91 | }
92 |
93 | if (updateFuture.isTicketAdded) {
94 | // ticket already added
95 | return;
96 | }
97 | updateFuture.isTicketAdded = true;
98 |
99 | final int references = this.chunksBeingWorkedOn.addTo(key, 1);
100 | if (references == 0) {
101 | final ChunkPos pos = new ChunkPos(chunkX, chunkZ);
102 | world.getChunkSource().addRegionTicket(StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos);
103 | }
104 |
105 | updateFuture.onComplete.thenAcceptAsync((final Void ignore) -> {
106 | final int newReferences = this.chunksBeingWorkedOn.get(key);
107 | if (newReferences == 1) {
108 | this.chunksBeingWorkedOn.remove(key);
109 | final ChunkPos pos = new ChunkPos(chunkX, chunkZ);
110 | world.getChunkSource().removeRegionTicket(StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos);
111 | } else {
112 | this.chunksBeingWorkedOn.put(key, newReferences - 1);
113 | }
114 | }, world.getChunkSource().chunkMap.mainThreadExecutor).whenComplete((final Void ignore, final Throwable thr) -> {
115 | if (thr != null) {
116 | LOGGER.error("Failed to remove ticket level for post chunk task " + new ChunkPos(chunkX, chunkZ), thr);
117 | }
118 | });
119 | }
120 |
121 | /**
122 | * @reason Redirect scheduling call away from the vanilla light engine, as well as enforce
123 | * that chunk neighbours are loaded before the processing can occur
124 | * @author Spottedleaf
125 | */
126 | @Overwrite
127 | public void checkBlock(final BlockPos pos) {
128 | final BlockPos posCopy = pos.immutable();
129 | this.queueTaskForSection(posCopy.getX() >> 4, posCopy.getY() >> 4, posCopy.getZ() >> 4, () -> {
130 | return this.getLightEngine().blockChange(posCopy);
131 | });
132 | }
133 |
134 | /**
135 | * @reason Avoid messing with the vanilla light engine state
136 | * @author Spottedleaf
137 | */
138 | @Overwrite
139 | public void updateChunkStatus(final ChunkPos pos) {}
140 |
141 | /**
142 | * @reason Redirect to schedule for our own logic, as well as ensure 1 radius neighbours
143 | * are loaded
144 | * Note: Our scheduling logic will discard this call if the chunk is not lit, unloaded, or not at LIGHT stage yet.
145 | * @author Spottedleaf
146 | */
147 | @Overwrite
148 | public void updateSectionStatus(final SectionPos pos, final boolean notReady) {
149 | this.queueTaskForSection(pos.getX(), pos.getY(), pos.getZ(), () -> {
150 | return this.getLightEngine().sectionChange(pos, notReady);
151 | });
152 | }
153 |
154 | /**
155 | * @reason Avoid messing with the vanilla light engine state
156 | * @author Spottedleaf
157 | */
158 | @Overwrite
159 | public void propagateLightSources(final ChunkPos pos) {
160 | // handled by light()
161 | }
162 |
163 | /**
164 | * @reason Avoid messing with the vanilla light engine state
165 | * @author Spottedleaf
166 | */
167 | @Overwrite
168 | public void setLightEnabled(final ChunkPos pos, final boolean lightEnabled) {
169 | // light impl does not need to do this
170 | }
171 |
172 | /**
173 | * @reason Light data is now attached to chunks, and this means we need to hook into chunk loading logic
174 | * to load the data rather than rely on this call. This call also would mess with the vanilla light engine state.
175 | * @author Spottedleaf
176 | */
177 | @Overwrite
178 | public void queueSectionData(final LightLayer lightType, final SectionPos pos, final @Nullable DataLayer nibbles) {
179 | // load hooks inside ChunkSerializer
180 | }
181 |
182 | /**
183 | * @reason Avoid messing with the vanilla light engine state
184 | * @author Spottedleaf
185 | */
186 | @Overwrite
187 | public void retainData(final ChunkPos pos, final boolean retainData) {
188 | // light impl does not need to do this
189 | }
190 |
191 | /**
192 | * @reason Starlight does not have to do this
193 | * @author Spottedleaf
194 | */
195 | @Overwrite
196 | public CompletableFuture initializeLight(final ChunkAccess chunk, final boolean lit) {
197 | return CompletableFuture.completedFuture(chunk);
198 | }
199 |
200 | /**
201 | * @reason Route to new logic to either light or just load the data
202 | * @author Spottedleaf
203 | */
204 | @Overwrite
205 | public CompletableFuture lightChunk(final ChunkAccess chunk, final boolean lit) {
206 | final ChunkPos chunkPos = chunk.getPos();
207 |
208 | return CompletableFuture.supplyAsync(() -> {
209 | final Boolean[] emptySections = StarLightEngine.getEmptySectionsForChunk(chunk);
210 | if (!lit) {
211 | chunk.setLightCorrect(false);
212 | this.getLightEngine().lightChunk(chunk, emptySections);
213 | chunk.setLightCorrect(true);
214 | } else {
215 | this.getLightEngine().forceLoadInChunk(chunk, emptySections);
216 | // can't really force the chunk to be edged checked, as we need neighbouring chunks - but we don't have
217 | // them, so if it's not loaded then i guess we can't do edge checks. later loads of the chunk should
218 | // catch what we miss here.
219 | this.getLightEngine().checkChunkEdges(chunkPos.x, chunkPos.z);
220 | }
221 |
222 | // this.chunkMap.releaseLightTicket(chunkPos); // vanilla 1.21 no longer does this
223 | return chunk;
224 | }, (runnable) -> {
225 | this.getLightEngine().scheduleChunkLight(chunkPos, runnable);
226 | this.tryScheduleUpdate();
227 | }).whenComplete((final ChunkAccess c, final Throwable throwable) -> {
228 | if (throwable != null) {
229 | LOGGER.error("Failed to light chunk " + chunkPos, throwable);
230 | }
231 | });
232 | }
233 |
234 | @Unique
235 | private final AtomicLong scalablelux$lastLightUpdate = new AtomicLong(0);
236 |
237 | @WrapOperation(method = "tryScheduleUpdate", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/lighting/LevelLightEngine;hasLightWork()Z"))
238 | private boolean scheduleOnlyWhenDirty(ThreadedLevelLightEngine instance, Operation original) {
239 | if (!GlobalExecutors.ENABLED) {
240 | return original.call(instance);
241 | }
242 | final boolean queueDirty = ((StarLightLightingProvider) instance).getLightEngine().isQueueDirty();
243 | if (queueDirty) {
244 | return original.call(instance);
245 | }
246 | final long lastUpdate = this.scalablelux$lastLightUpdate.get();
247 | final long currentTime = System.nanoTime();
248 | if (currentTime - lastUpdate >= 10_000_000L) { // 10ms
249 | if (this.scalablelux$lastLightUpdate.compareAndSet(lastUpdate, currentTime)) {
250 | return original.call(instance);
251 | }
252 | }
253 | return false;
254 | }
255 |
256 | /**
257 | * @author ishland
258 | * @reason implement waitForPendingTasks hook
259 | */
260 | @Overwrite
261 | public CompletableFuture> waitForPendingTasks(int x, int z) {
262 | return this.getLightEngine().syncFuture(x, z);
263 | }
264 | }
265 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/mixin/common/world/LevelMixin.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.mixin.common.world;
2 |
3 | import ca.spottedleaf.starlight.common.world.ExtendedWorld;
4 | import net.minecraft.world.level.Level;
5 | import net.minecraft.world.level.LevelAccessor;
6 | import net.minecraft.world.level.chunk.ChunkAccess;
7 | import net.minecraft.world.level.chunk.status.ChunkStatus;
8 | import net.minecraft.world.level.chunk.LevelChunk;
9 | import org.spongepowered.asm.mixin.Mixin;
10 |
11 | @Mixin(Level.class)
12 | public abstract class LevelMixin implements LevelAccessor, AutoCloseable, ExtendedWorld {
13 |
14 | @Override
15 | public LevelChunk getChunkAtImmediately(final int chunkX, final int chunkZ) {
16 | return this.getChunkSource().getChunk(chunkX, chunkZ, false);
17 | }
18 |
19 | @Override
20 | public ChunkAccess getAnyChunkImmediately(final int chunkX, final int chunkZ) {
21 | return this.getChunkSource().getChunk(chunkX, chunkX, ChunkStatus.EMPTY, false);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/mixin/common/world/SerializableChunkDataMixin.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.mixin.common.world;
2 |
3 | import ca.spottedleaf.starlight.common.config.Config;
4 | import ca.spottedleaf.starlight.common.light.SWMRNibbleArray;
5 | import ca.spottedleaf.starlight.common.util.SaveUtil;
6 | import ca.spottedleaf.starlight.common.world.ExtendedSerializableChunkData;
7 | import net.minecraft.core.RegistryAccess;
8 | import net.minecraft.nbt.CompoundTag;
9 | import net.minecraft.server.level.ServerLevel;
10 | import net.minecraft.world.entity.ai.village.poi.PoiManager;
11 | import net.minecraft.world.level.ChunkPos;
12 | import net.minecraft.world.level.LevelHeightAccessor;
13 | import net.minecraft.world.level.chunk.ChunkAccess;
14 | import net.minecraft.world.level.chunk.ProtoChunk;
15 | import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
16 | import net.minecraft.world.level.chunk.storage.SerializableChunkData;
17 | import org.spongepowered.asm.mixin.*;
18 | import org.spongepowered.asm.mixin.injection.At;
19 | import org.spongepowered.asm.mixin.injection.Inject;
20 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
21 |
22 | @Mixin(SerializableChunkData.class)
23 | public abstract class SerializableChunkDataMixin implements ExtendedSerializableChunkData {
24 |
25 | @Mutable
26 | @Shadow
27 | @Final
28 | private boolean lightCorrect;
29 | @Unique
30 | private SWMRNibbleArray.SaveState[] scalablelux$blocklight;
31 | @Unique
32 | private SWMRNibbleArray.SaveState[] scalablelux$skylight;
33 | @Unique
34 | private boolean scalablelux$actuallyCorrect;
35 |
36 | @Override
37 | public void scalablelux$setBlockLight(SWMRNibbleArray.SaveState[] light) {
38 | this.scalablelux$blocklight = light;
39 | }
40 |
41 | @Override
42 | public void scalablelux$setSkyLight(SWMRNibbleArray.SaveState[] light) {
43 | this.scalablelux$skylight = light;
44 | }
45 |
46 | @Override
47 | public void scalableLux$setActuallyCorrect(boolean correct) {
48 | this.scalablelux$actuallyCorrect = correct;
49 | }
50 |
51 | @Override
52 | public SWMRNibbleArray.SaveState[] scalablelux$getBlockLight() {
53 | return this.scalablelux$blocklight;
54 | }
55 |
56 | @Override
57 | public SWMRNibbleArray.SaveState[] scalablelux$getSkyLight() {
58 | return this.scalablelux$skylight;
59 | }
60 |
61 | @Override
62 | public boolean scalablelux$getActuallyCorrect() {
63 | return this.scalablelux$actuallyCorrect;
64 | }
65 |
66 | @Override
67 | public void scalablelux$setLightCorrect(boolean correct) {
68 | this.lightCorrect = correct;
69 | }
70 |
71 | /**
72 | * Overwrites vanilla's light data with our own.
73 | * TODO this needs to be checked on update to account for format changes
74 | */
75 | @Inject(
76 | method = "copyOf",
77 | at = @At("RETURN")
78 | )
79 | private static void prepareSaveLightHook(ServerLevel world, ChunkAccess chunk, CallbackInfoReturnable cir) {
80 | if (Config.USE_STARLIGHT_FORMAT) {
81 | SaveUtil.prepareSaveLightHook(chunk, cir.getReturnValue());
82 | } else {
83 | SaveUtil.prepareSaveVanillaLightHook(world, chunk, cir.getReturnValue());
84 | }
85 | }
86 |
87 | @Inject(
88 | method = "write",
89 | at = @At("RETURN")
90 | )
91 | private void saveLightHook(CallbackInfoReturnable cir) {
92 | if (Config.USE_STARLIGHT_FORMAT) {
93 | SaveUtil.saveLightHook((SerializableChunkData) (Object) this, cir.getReturnValue());
94 | }
95 | }
96 |
97 | @Inject(
98 | method = "parse",
99 | at = @At("RETURN")
100 | )
101 | private static void prepareLoadLightHook(LevelHeightAccessor levelHeightAccessor, RegistryAccess registryAccess, CompoundTag compoundTag, CallbackInfoReturnable cir) {
102 | if (Config.USE_STARLIGHT_FORMAT) {
103 | SaveUtil.prepareLoadLightHook(levelHeightAccessor, compoundTag, cir.getReturnValue());
104 | }
105 | }
106 |
107 | /**
108 | * Loads our light data into the returned chunk object from the tag.
109 | * TODO this needs to be checked on update to account for format changes
110 | */
111 | @Inject(
112 | method = "read",
113 | at = @At("RETURN")
114 | )
115 | private void loadLightHook(ServerLevel serverLevel, PoiManager poiManager, RegionStorageInfo regionStorageInfo, ChunkPos chunkPos, CallbackInfoReturnable cir) {
116 | if (Config.USE_STARLIGHT_FORMAT) {
117 | SaveUtil.loadLightHook(serverLevel, (SerializableChunkData) (Object) this, cir.getReturnValue());
118 | } else {
119 | SaveUtil.loadVanillaLightHook(serverLevel, (SerializableChunkData) (Object) this, cir.getReturnValue());
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/mixin/common/world/ServerWorldMixin.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.mixin.common.world;
2 |
3 | import ca.spottedleaf.starlight.common.util.CoordinateUtils;
4 | import ca.spottedleaf.starlight.common.world.ExtendedWorld;
5 | import net.minecraft.core.Holder;
6 | import net.minecraft.core.RegistryAccess;
7 | import net.minecraft.resources.ResourceKey;
8 | import net.minecraft.server.level.ChunkHolder;
9 | import net.minecraft.server.level.ChunkMap;
10 | import net.minecraft.server.level.ChunkResult;
11 | import net.minecraft.server.level.ServerChunkCache;
12 | import net.minecraft.server.level.ServerLevel;
13 | import net.minecraft.util.profiling.ProfilerFiller;
14 | import net.minecraft.world.level.Level;
15 | import net.minecraft.world.level.WorldGenLevel;
16 | import net.minecraft.world.level.chunk.ChunkAccess;
17 | import net.minecraft.world.level.chunk.LevelChunk;
18 | import net.minecraft.world.level.dimension.DimensionType;
19 | import net.minecraft.world.level.storage.WritableLevelData;
20 | import org.spongepowered.asm.mixin.Final;
21 | import org.spongepowered.asm.mixin.Mixin;
22 | import org.spongepowered.asm.mixin.Shadow;
23 | import java.util.function.Supplier;
24 |
25 | @Mixin(ServerLevel.class)
26 | public abstract class ServerWorldMixin extends Level implements WorldGenLevel, ExtendedWorld {
27 |
28 | @Shadow
29 | @Final
30 | private ServerChunkCache chunkSource;
31 |
32 | protected ServerWorldMixin(WritableLevelData writableLevelData, ResourceKey resourceKey, RegistryAccess registryAccess, Holder holder, boolean bl, boolean bl2, long l, int i) {
33 | super(writableLevelData, resourceKey, registryAccess, holder, bl, bl2, l, i);
34 | }
35 |
36 | @Override
37 | public final LevelChunk getChunkAtImmediately(final int chunkX, final int chunkZ) {
38 | final ChunkMap storage = this.chunkSource.chunkMap;
39 | final ChunkHolder holder = storage.getVisibleChunkIfPresent(CoordinateUtils.getChunkKey(chunkX, chunkZ));
40 |
41 | if (holder == null) {
42 | return null;
43 | }
44 |
45 | final ChunkResult result = holder.getFullChunkFuture().getNow(null);
46 |
47 | return result == null ? null : result.orElse(null);
48 | }
49 |
50 | @Override
51 | public final ChunkAccess getAnyChunkImmediately(final int chunkX, final int chunkZ) {
52 | final ChunkMap storage = this.chunkSource.chunkMap;
53 | final ChunkHolder holder = storage.getVisibleChunkIfPresent(CoordinateUtils.getChunkKey(chunkX, chunkZ));
54 |
55 | return holder == null ? null : holder.getLatestChunk();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/ca/spottedleaf/starlight/mixin/common/world/WorldGenRegionMixin.java:
--------------------------------------------------------------------------------
1 | package ca.spottedleaf.starlight.mixin.common.world;
2 |
3 | import net.minecraft.core.BlockPos;
4 | import net.minecraft.server.level.WorldGenRegion;
5 | import net.minecraft.world.level.LightLayer;
6 | import net.minecraft.world.level.WorldGenLevel;
7 | import net.minecraft.world.level.chunk.ChunkAccess;
8 | import org.spongepowered.asm.mixin.Mixin;
9 | import org.spongepowered.asm.mixin.Shadow;
10 |
11 | @Mixin(WorldGenRegion.class)
12 | public abstract class WorldGenRegionMixin implements WorldGenLevel {
13 |
14 | @Shadow
15 | public abstract ChunkAccess getChunk(int i, int j);
16 |
17 | /**
18 | * @reason During feature generation, light data is not initialised and will always return 15 in Starlight. Vanilla
19 | * can possibly return 0 if partially initialised, which allows some mushroom blocks to generate.
20 | * In general, the brightness value from the light engine should not be used until the chunk is ready. To emulate
21 | * Vanilla behavior better, we return 0 as the brightness during world gen unless the target chunk is finished
22 | * lighting.
23 | * @author Spottedleaf
24 | */
25 | @Override
26 | public int getBrightness(final LightLayer lightLayer, final BlockPos blockPos) {
27 | final ChunkAccess chunk = this.getChunk(blockPos.getX() >> 4, blockPos.getZ() >> 4);
28 | if (!chunk.isLightCorrect()) {
29 | return 0;
30 | }
31 | return this.getLightEngine().getLayerListener(lightLayer).getLightValue(blockPos);
32 | }
33 |
34 | /**
35 | * @reason See above
36 | * @author Spottedleaf
37 | */
38 | @Override
39 | public int getRawBrightness(final BlockPos blockPos, final int subtract) {
40 | final ChunkAccess chunk = this.getChunk(blockPos.getX() >> 4, blockPos.getZ() >> 4);
41 | if (!chunk.isLightCorrect()) {
42 | return 0;
43 | }
44 | return this.getLightEngine().getRawBrightness(blockPos, subtract);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/resources/assets/scalablelux/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RelativityMC/ScalableLux/c6ec02104ec2104fbb3c99f9782641aa3b5c6515/src/main/resources/assets/scalablelux/icon.png
--------------------------------------------------------------------------------
/src/main/resources/fabric.mod.json:
--------------------------------------------------------------------------------
1 | {
2 | "schemaVersion": 1,
3 | "id": "scalablelux",
4 | "version": "${version}",
5 | "name": "ScalableLux",
6 | "description": "Rewrites the light engine to fix lighting performance and lighting errors",
7 | "authors": [
8 | "Spottedleaf",
9 | "ishland"
10 | ],
11 | "contact": {
12 | "issues": "https://github.com/RelativityMC/ScalableLux/issues",
13 | "sources": "https://github.com/RelativityMC/ScalableLux"
14 | },
15 | "provides": [
16 | "starlight"
17 | ],
18 | "breaks": {
19 | "phosphor" : "*"
20 | },
21 | "license": "LGPL-3.0-only",
22 | "icon": "assets/scalablelux/icon.png",
23 | "environment": "*",
24 | "entrypoints": {
25 | "main": [
26 | "ca.spottedleaf.starlight.common.ScalableLuxEntrypoint"
27 | ]
28 | },
29 | "mixins": [
30 | "scalablelux.mixins.json"
31 | ],
32 | "accessWidener" : "scalablelux.accesswidener",
33 | "depends": {
34 | "fabricloader": ">=${loader_version}",
35 | "minecraft": ">1.21.1"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/resources/scalablelux.accesswidener:
--------------------------------------------------------------------------------
1 | accessWidener v1 named
2 |
3 | # BlockStateBase.ShapeCache
4 | accessible class net/minecraft/world/level/block/state/BlockBehaviour$BlockStateBase$Cache
5 | #accessible field net/minecraft/world/level/block/state/BlockBehaviour$BlockStateBase$Cache lightBlock I
6 |
7 |
8 | # LevelChunkSection
9 | accessible field net/minecraft/world/level/chunk/LevelChunkSection states Lnet/minecraft/world/level/chunk/PalettedContainer;
10 |
11 |
12 | # PalettedContainer
13 | accessible method net/minecraft/world/level/chunk/PalettedContainer get (I)Ljava/lang/Object;
14 |
15 |
16 | # ChunkMap
17 | accessible field net/minecraft/server/level/ChunkMap level Lnet/minecraft/server/level/ServerLevel;
18 | accessible field net/minecraft/server/level/ChunkMap mainThreadExecutor Lnet/minecraft/util/thread/BlockableEventLoop;
19 |
20 | accessible method net/minecraft/server/level/ChunkMap getUpdatingChunkIfPresent (J)Lnet/minecraft/server/level/ChunkHolder;
21 | accessible method net/minecraft/server/level/ChunkMap getVisibleChunkIfPresent (J)Lnet/minecraft/server/level/ChunkHolder;
22 | accessible method net/minecraft/server/level/ChunkMap getChunkQueueLevel (J)Ljava/util/function/IntSupplier;
23 |
24 | # LevelLightEngine
25 | mutable field net/minecraft/world/level/lighting/LevelLightEngine blockEngine Lnet/minecraft/world/level/lighting/LightEngine;
26 | mutable field net/minecraft/world/level/lighting/LevelLightEngine skyEngine Lnet/minecraft/world/level/lighting/LightEngine;
27 |
28 | # ThreadedLevelLightEngine
29 | accessible class net/minecraft/server/level/ThreadedLevelLightEngine$TaskType
30 |
--------------------------------------------------------------------------------
/src/main/resources/scalablelux.mixins.json:
--------------------------------------------------------------------------------
1 | {
2 | "required": true,
3 | "minVersion": "0.8",
4 | "package": "ca.spottedleaf.starlight.mixin",
5 | "compatibilityLevel": "JAVA_17",
6 | "mixins": [
7 | "common.blockstate.BlockStateBaseMixin",
8 | "common.chunk.ChunkAccessMixin",
9 | "common.chunk.EmptyLevelChunkMixin",
10 | "common.chunk.ImposterProtoChunkMixin",
11 | "common.chunk.LevelChunkMixin",
12 | "common.chunk.ProtoChunkMixin",
13 | "common.lightengine.LevelLightEngineMixin",
14 | "common.lightengine.ThreadedLevelLightEngineMixin",
15 | "common.world.SerializableChunkDataMixin",
16 | "common.world.LevelMixin",
17 | "common.world.ServerWorldMixin",
18 | "common.world.WorldGenRegionMixin"
19 | ],
20 | "client": [
21 | "client.multiplayer.ClientPacketListenerMixin",
22 | "client.world.ClientLevelMixin"
23 | ],
24 | "injectors": {
25 | "defaultRequire": 1
26 | }
27 | }
28 |
--------------------------------------------------------------------------------