mirror of
https://github.com/GTNewHorizons/ForgeMultipart.git
synced 2026-11-12 16:37:38 +00:00
refactor: port TickScheduler to Java
Converts the scheduler, its two extension classes and PartTickEntry. No jar in the pack references this class, so the ABI constraint was minimal, but the reference shape was kept anyway: TickScheduler$ and PartTickEntry are descriptor-identical including the Scala-style accessor pairs, and the statics class adds only a private constructor. TickScheduler$ is the one companion singleton in this migration that is not a pure forwarder. The object extends WorldExtensionInstantiator and WorldExtensionManager is handed the instance itself, so the two instantiator hooks stay on the singleton. processTicks filtered a ListBuffer with a side-effecting predicate that fires part callbacks and mutates the entry; the Java version iterates and rebuilds in the same order, so callback order and the reschedule mutation are unchanged. One Scala call site changed: the registration now passes TickScheduler$.MODULE$. TRandomUpdateTick needed casts because a Scala trait extending a class is a bare interface in bytecode, the same pattern already recorded for JIconHitEffects. Five anonfun classes removed. Validated with 90 plain-JVM tests, a clean build, and the Java 8 Forge dedicated-server suite, which runs this code every tick because the scheduler is registered as a world extension at startup.
This commit is contained in:
1 parent
1b9eb05726
commit
213ff0c8ea
5 files changed
+442
-260
No files matched your search
@@ -740,3 +740,59 @@ removed. The Java loop produces no replacement class.
|
||||
- Java 8 Forge dedicated-server suite: 5 tests, 0 failures, 0 errors, covering real part registration and
|
||||
instantiation of `mc_torch` and `mcr_face` plus composite tile generation.
|
||||
- Ray tracing, rendering and particle paths need a client and stay on the manual checklist.
|
||||
|
||||
## 2026-08-14 — TickScheduler Java port
|
||||
|
||||
No jar in the pack references this class, so the ABI constraint was minimal, but the reference shape was preserved
|
||||
anyway for consistency with the other ports.
|
||||
|
||||
### Observable behavior
|
||||
|
||||
No known divergence. Ticks scheduled while the world is mid-tick are still queued and applied after processing rather
|
||||
than during it, an existing entry for the same part is still only overridden when going from random to scheduled,
|
||||
random-update parts still reschedule themselves 800 to 1600 ticks out, chunks are still dropped from the active set
|
||||
once their list empties, and only non-random entries are still written to chunk NBT.
|
||||
|
||||
`processTicks` filtered a `ListBuffer` with a side-effecting predicate that fires the part callbacks and mutates the
|
||||
entry. The Java version iterates and rebuilds the list in the same order, so callback order and the reschedule
|
||||
mutation are unchanged.
|
||||
|
||||
### The singleton carries behavior here
|
||||
|
||||
Unlike the other companion singletons in this migration, `TickScheduler$` is not a pure forwarder. The object extends
|
||||
`WorldExtensionInstantiator` and `WorldExtensionManager.registerWorldExtension` is handed the instance itself, so the
|
||||
two instantiator hooks have to live on the singleton. It stays a Java class extending `WorldExtensionInstantiator`
|
||||
with `MODULE$`, delegating into the statics.
|
||||
|
||||
### Supported JVM API
|
||||
|
||||
- `TickScheduler$` and `TickScheduler$PartTickEntry` are descriptor-identical to the reference, including
|
||||
`PartTickEntry`'s Scala-style `time()`, `time_$eq`, `random()` and `random_$eq` accessor pairs and both
|
||||
constructors.
|
||||
- `TickScheduler` adds only a private constructor.
|
||||
- `WorldTickScheduler` and `ChunkTickScheduler` were Scala `private class` and are now package-private static nested
|
||||
classes, keeping their `TickScheduler$WorldTickScheduler` and `TickScheduler$ChunkTickScheduler` binary names.
|
||||
|
||||
### Recompiled Scala consumers
|
||||
|
||||
One call site. `WorldExtensionManager.registerWorldExtension(TickScheduler)` passed the object as a value and must now
|
||||
pass `TickScheduler$.MODULE$`.
|
||||
|
||||
`TRandomUpdateTick` is a Scala trait extending `TMultiPart`, so at bytecode level it is a bare interface that does not
|
||||
extend the class. The Java code casts where the reference relied on Scala's view of the type, the same pattern already
|
||||
recorded for `JIconHitEffects`.
|
||||
|
||||
### Compiler artifacts
|
||||
|
||||
Accepted divergence: the five `$$anonfun$` classes under `WorldTickScheduler.postTick` and `ChunkTickScheduler`'s
|
||||
`processTicks`, `saveData` and `loadData` are removed. The Java loops produce no replacement classes.
|
||||
|
||||
### Validation
|
||||
|
||||
- Complete plain-JVM suite: 90 tests, 0 failures, 0 errors.
|
||||
- Clean `spotlessApply checkstyleTest build`: passing.
|
||||
- Java 8 Forge dedicated-server suite: 5 tests, 0 failures, 0 errors. This exercises the port directly rather than
|
||||
incidentally: the scheduler is registered as a world extension during startup, so `createWorldExtension`, `load`,
|
||||
and the `preTick`/`postTick` pair all run for every server tick of the suite.
|
||||
- Scheduled and random tick firing over time, and the chunk NBT round trip, are not asserted by any test and stay on
|
||||
the manual checklist.
|
||||
@@ -0,0 +1,41 @@
|
||||
package codechicken.multipart;
|
||||
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
|
||||
import codechicken.lib.world.ChunkExtension;
|
||||
import codechicken.lib.world.WorldExtension;
|
||||
import codechicken.lib.world.WorldExtensionInstantiator;
|
||||
|
||||
/**
|
||||
* Scala companion singleton. This one is not just a forwarder: WorldExtensionManager registers the instance itself, so
|
||||
* the instantiator hooks have to live here.
|
||||
*/
|
||||
public final class TickScheduler$ extends WorldExtensionInstantiator {
|
||||
|
||||
public static final TickScheduler$ MODULE$ = new TickScheduler$();
|
||||
|
||||
private TickScheduler$() {}
|
||||
|
||||
@Override
|
||||
public WorldExtension createWorldExtension(World world) {
|
||||
return TickScheduler.createWorldExtension(world);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkExtension createChunkExtension(Chunk chunk, WorldExtension world) {
|
||||
return TickScheduler.createChunkExtension(chunk, world);
|
||||
}
|
||||
|
||||
public void loadRandomTick(TRandomUpdateTick part) {
|
||||
TickScheduler.loadRandomTick(part);
|
||||
}
|
||||
|
||||
public void scheduleTick(TMultiPart part, int ticks) {
|
||||
TickScheduler.scheduleTick(part, ticks);
|
||||
}
|
||||
|
||||
public long getSchedulerTime(World world) {
|
||||
return TickScheduler.getSchedulerTime(world);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
package codechicken.multipart;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import net.minecraft.nbt.CompressedStreamTools;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
import net.minecraft.world.ChunkCoordIntPair;
|
||||
import net.minecraft.world.ChunkPosition;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraft.world.storage.SaveHandler;
|
||||
import net.minecraftforge.common.DimensionManager;
|
||||
|
||||
import codechicken.lib.vec.BlockCoord;
|
||||
import codechicken.lib.world.ChunkExtension;
|
||||
import codechicken.lib.world.WorldExtension;
|
||||
import codechicken.multipart.handler.MultipartProxy;
|
||||
|
||||
/**
|
||||
* Used for scheduling delayed callbacks to parts. Do not use this for redstone applications that require precise
|
||||
* timing. If 2 parts are both scheduled for an update on the same tick, there is no guarantee which one will update
|
||||
* first. These parts should not depend on a state of another part that may have changed before/after them.
|
||||
*/
|
||||
public final class TickScheduler {
|
||||
|
||||
private TickScheduler() {}
|
||||
|
||||
public static class PartTickEntry {
|
||||
|
||||
private final TMultiPart part;
|
||||
private long time;
|
||||
private boolean random;
|
||||
|
||||
public PartTickEntry(TMultiPart part, long time, boolean random) {
|
||||
this.part = part;
|
||||
this.time = time;
|
||||
this.random = random;
|
||||
}
|
||||
|
||||
public PartTickEntry(TMultiPart part, int ticks) {
|
||||
this(part, ticks, false);
|
||||
}
|
||||
|
||||
public TMultiPart part() {
|
||||
return part;
|
||||
}
|
||||
|
||||
public long time() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void time_$eq(long time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public boolean random() {
|
||||
return random;
|
||||
}
|
||||
|
||||
public void random_$eq(boolean random) {
|
||||
this.random = random;
|
||||
}
|
||||
}
|
||||
|
||||
static class WorldTickScheduler extends WorldExtension {
|
||||
|
||||
private long schedTime = 0L;
|
||||
private Set<ChunkTickScheduler> tickChunks = new HashSet<>();
|
||||
private boolean processing = false;
|
||||
private final List<PartTickEntry> pending = new ArrayList<>();
|
||||
|
||||
WorldTickScheduler(World world) {
|
||||
super(world);
|
||||
}
|
||||
|
||||
void scheduleTick(TMultiPart part, int ticks, boolean random) {
|
||||
if (processing) {
|
||||
pending.add(new PartTickEntry(part, schedTime + ticks, random));
|
||||
} else {
|
||||
doScheduleTick(part, schedTime + ticks, random);
|
||||
}
|
||||
}
|
||||
|
||||
void doScheduleTick(TMultiPart part, long time, boolean random) {
|
||||
if (part.tile() != null) {
|
||||
((ChunkTickScheduler) getChunkExtension(part.tile().xCoord >> 4, part.tile().zCoord >> 4))
|
||||
.scheduleTick(part, time, random);
|
||||
}
|
||||
}
|
||||
|
||||
void loadRandom(TRandomUpdateTick part) {
|
||||
scheduleTick((TMultiPart) part, nextRandomTick(), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preTick() {
|
||||
processing = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postTick() {
|
||||
if (!tickChunks.isEmpty()) {
|
||||
Set<ChunkTickScheduler> remaining = new HashSet<>();
|
||||
for (ChunkTickScheduler chunk : tickChunks) {
|
||||
if (chunk.processTicks()) {
|
||||
remaining.add(chunk);
|
||||
}
|
||||
}
|
||||
tickChunks = remaining;
|
||||
}
|
||||
|
||||
processing = false;
|
||||
for (PartTickEntry e : pending) {
|
||||
doScheduleTick(e.part(), e.time(), e.random());
|
||||
}
|
||||
pending.clear();
|
||||
|
||||
schedTime += 1;
|
||||
}
|
||||
|
||||
File saveDir() {
|
||||
// Calling DimensionManager.getCurrentSaveRootDirectory too early breaks game saves, we have a world
|
||||
// reference, use it
|
||||
if (world.provider.dimensionId == 0) {
|
||||
return ((SaveHandler) world.getSaveHandler()).getWorldDirectory();
|
||||
}
|
||||
|
||||
return new File(DimensionManager.getCurrentSaveRootDirectory(), world.provider.getSaveFolder());
|
||||
}
|
||||
|
||||
File saveFile() {
|
||||
return new File(saveDir(), "multipart.dat");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
try {
|
||||
FileInputStream in = new FileInputStream(saveFile());
|
||||
loadTag(CompressedStreamTools.readCompressed(in));
|
||||
in.close();
|
||||
} catch (Exception e) {
|
||||
// Matches the reference, which swallows any failure to read the saved schedule.
|
||||
}
|
||||
|
||||
loadTag(new NBTTagCompound());
|
||||
}
|
||||
|
||||
void loadTag(NBTTagCompound tag) {
|
||||
if (tag.hasKey("schedTime")) {
|
||||
schedTime = tag.getLong("schedTime");
|
||||
} else {
|
||||
schedTime = world.getTotalWorldTime();
|
||||
}
|
||||
}
|
||||
|
||||
NBTTagCompound saveTag() {
|
||||
NBTTagCompound tag = new NBTTagCompound();
|
||||
tag.setLong("schedTime", schedTime);
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
try {
|
||||
File file = saveFile();
|
||||
if (!file.getParentFile().exists()) {
|
||||
file.getParentFile().mkdirs();
|
||||
}
|
||||
if (!file.exists()) {
|
||||
file.createNewFile();
|
||||
}
|
||||
|
||||
DataOutputStream dout = new DataOutputStream(new FileOutputStream(file));
|
||||
CompressedStreamTools.writeCompressed(saveTag(), dout);
|
||||
dout.close();
|
||||
} catch (java.io.IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
int nextRandomTick() {
|
||||
return world.rand.nextInt(800) + 800;
|
||||
}
|
||||
}
|
||||
|
||||
static class ChunkTickScheduler extends ChunkExtension {
|
||||
|
||||
private List<PartTickEntry> tickList = new ArrayList<>();
|
||||
|
||||
ChunkTickScheduler(Chunk chunk, WorldTickScheduler world) {
|
||||
super(chunk, world);
|
||||
}
|
||||
|
||||
private WorldTickScheduler scheduler() {
|
||||
return (WorldTickScheduler) world;
|
||||
}
|
||||
|
||||
long schedTime() {
|
||||
return scheduler().schedTime;
|
||||
}
|
||||
|
||||
void scheduleTick(TMultiPart part, long time, boolean random) {
|
||||
for (PartTickEntry e : tickList) {
|
||||
if (e.part() == part) {
|
||||
// only override an existing tick if we're going from random to scheduled
|
||||
if (e.random() && !random) {
|
||||
e.time_$eq(time);
|
||||
e.random_$eq(random);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
tickList.add(new PartTickEntry(part, time, random));
|
||||
if (tickList.size() == 1) {
|
||||
scheduler().tickChunks.add(this);
|
||||
}
|
||||
}
|
||||
|
||||
int nextRandomTick() {
|
||||
return scheduler().nextRandomTick();
|
||||
}
|
||||
|
||||
boolean processTicks() {
|
||||
List<PartTickEntry> kept = new ArrayList<>();
|
||||
for (PartTickEntry e : tickList) {
|
||||
if (processTick(e)) {
|
||||
kept.add(e);
|
||||
}
|
||||
}
|
||||
tickList = kept;
|
||||
return !tickList.isEmpty();
|
||||
}
|
||||
|
||||
boolean processTick(PartTickEntry e) {
|
||||
if (e.time() <= schedTime()) {
|
||||
if (e.part().tile() != null) {
|
||||
if (e.random() && e.part() instanceof TRandomUpdateTick) {
|
||||
((TRandomUpdateTick) e.part()).randomUpdate();
|
||||
} else {
|
||||
e.part().scheduledTick();
|
||||
}
|
||||
|
||||
if (e.part() instanceof TRandomUpdateTick) {
|
||||
e.time_$eq(schedTime() + nextRandomTick());
|
||||
e.random_$eq(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveData(NBTTagCompound data) {
|
||||
NBTTagList tagList = new NBTTagList();
|
||||
for (PartTickEntry e : tickList) {
|
||||
TMultiPart part = e.part();
|
||||
if (part.tile() != null && !e.random()) {
|
||||
NBTTagCompound tag = new NBTTagCompound();
|
||||
tag.setShort("pos", (short) MultipartProxy.indexInChunk(new BlockCoord(part.tile())));
|
||||
tag.setByte("i", (byte) part.tile().jPartList().indexOf(part));
|
||||
tag.setLong("time", e.time());
|
||||
tagList.appendTag(tag);
|
||||
}
|
||||
}
|
||||
if (tagList.tagCount() > 0) {
|
||||
data.setTag("multipartTicks", tagList);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadData(NBTTagCompound data) {
|
||||
tickList.clear();
|
||||
if (!data.hasKey("multipartTicks")) {
|
||||
return;
|
||||
}
|
||||
|
||||
NBTTagList tagList = data.getTagList("multipartTicks", 10);
|
||||
ChunkCoordIntPair cc = new ChunkCoordIntPair(0, 0);
|
||||
for (int i = 0; i < tagList.tagCount(); i++) {
|
||||
NBTTagCompound tag = tagList.getCompoundTagAt(i);
|
||||
BlockCoord pos = MultipartProxy.indexInChunk(cc, tag.getShort("pos"));
|
||||
Object tile = chunk.chunkTileEntityMap.get(new ChunkPosition(pos.x, pos.y, pos.z));
|
||||
if (tile instanceof TileMultipart) {
|
||||
tickList.add(
|
||||
new PartTickEntry(
|
||||
((TileMultipart) tile).jPartList().get(tag.getByte("i")),
|
||||
tag.getLong("time"),
|
||||
false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
if (!tickList.isEmpty()) {
|
||||
scheduler().tickChunks.remove(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static WorldExtension createWorldExtension(World world) {
|
||||
return new WorldTickScheduler(world);
|
||||
}
|
||||
|
||||
public static ChunkExtension createChunkExtension(Chunk chunk, WorldExtension world) {
|
||||
return new ChunkTickScheduler(chunk, (WorldTickScheduler) world);
|
||||
}
|
||||
|
||||
public static WorldExtension getExtension(World world) {
|
||||
return TickScheduler$.MODULE$.getExtension(world);
|
||||
}
|
||||
|
||||
/** Start random ticking for a part. Should be called from TMultiPart.onWorldJoin. */
|
||||
public static void loadRandomTick(TRandomUpdateTick part) {
|
||||
TMultiPart multiPart = (TMultiPart) part;
|
||||
((WorldTickScheduler) getExtension(multiPart.tile().getWorldObj())).loadRandom(part);
|
||||
}
|
||||
|
||||
/** Schedule a tick for part relative to the current time. */
|
||||
public static void scheduleTick(TMultiPart part, int ticks) {
|
||||
((WorldTickScheduler) getExtension(part.tile().getWorldObj())).scheduleTick(part, ticks, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current scheduler time. Like the world time, but unaffected by the time set command and other things
|
||||
* changing time of day.
|
||||
*
|
||||
* @deprecated in favor of world.getTotalWorldTime
|
||||
*/
|
||||
@Deprecated
|
||||
public static long getSchedulerTime(World world) {
|
||||
return ((WorldTickScheduler) getExtension(world)).schedTime;
|
||||
}
|
||||
}
|
||||
@@ -1,259 +0,0 @@
|
||||
package codechicken.multipart
|
||||
|
||||
import codechicken.lib.world.WorldExtensionInstantiator
|
||||
import codechicken.lib.world.WorldExtension
|
||||
import codechicken.lib.world.ChunkExtension
|
||||
import net.minecraft.world.chunk.Chunk
|
||||
import net.minecraft.world.World
|
||||
import net.minecraft.nbt.{NBTTagCompound, NBTTagList, CompressedStreamTools}
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import codechicken.lib.vec.BlockCoord
|
||||
import net.minecraft.world.ChunkCoordIntPair
|
||||
import net.minecraft.world.ChunkPosition
|
||||
import scala.collection.mutable.HashSet
|
||||
import java.io.DataOutputStream
|
||||
import net.minecraftforge.common.DimensionManager
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.FileInputStream
|
||||
import net.minecraft.world.storage.SaveHandler
|
||||
|
||||
/** Used for scheduling delayed callbacks to parts. Do not use this for redstone
|
||||
* applications that require precise timing. If 2 parts are both scheduled for
|
||||
* an update on the same tick, there is no guarantee which one will update
|
||||
* first. These parts should not depend on a state of another part that may
|
||||
* have changed before/after them.
|
||||
*/
|
||||
object TickScheduler extends WorldExtensionInstantiator {
|
||||
class PartTickEntry(
|
||||
val part: TMultiPart,
|
||||
var time: Long,
|
||||
var random: Boolean
|
||||
) {
|
||||
def this(part: TMultiPart, ticks: Int) = this(part, ticks, false)
|
||||
}
|
||||
|
||||
private class WorldTickScheduler(world$ : World)
|
||||
extends WorldExtension(world$) {
|
||||
var schedTime = 0L
|
||||
var tickChunks = HashSet[ChunkTickScheduler]()
|
||||
private var processing = false
|
||||
private val pending = ListBuffer[PartTickEntry]()
|
||||
|
||||
def scheduleTick(part: TMultiPart, ticks: Int, random: Boolean) {
|
||||
if (processing)
|
||||
pending += new PartTickEntry(part, schedTime + ticks, random)
|
||||
else
|
||||
_scheduleTick(part, schedTime + ticks, random)
|
||||
}
|
||||
|
||||
def _scheduleTick(part: TMultiPart, time: Long, random: Boolean) {
|
||||
if (part.tile != null)
|
||||
getChunkExtension(part.tile.xCoord >> 4, part.tile.zCoord >> 4)
|
||||
.asInstanceOf[ChunkTickScheduler]
|
||||
.scheduleTick(part, time, random)
|
||||
}
|
||||
|
||||
def loadRandom(part: TRandomUpdateTick) =
|
||||
scheduleTick(part, nextRandomTick, true)
|
||||
|
||||
override def preTick() {
|
||||
processing = true
|
||||
}
|
||||
|
||||
override def postTick() {
|
||||
if (!tickChunks.isEmpty)
|
||||
tickChunks = tickChunks.filter(_.processTicks())
|
||||
|
||||
processing = false
|
||||
pending.foreach(e => _scheduleTick(e.part, e.time, e.random))
|
||||
pending.clear()
|
||||
|
||||
schedTime += 1
|
||||
}
|
||||
|
||||
def saveDir: File = {
|
||||
if (
|
||||
world.provider.dimensionId == 0
|
||||
) // Calling DimensionManager.getCurrentSaveRootDirectory too early breaks game saves, we have a world reference, use it
|
||||
return world.getSaveHandler.asInstanceOf[SaveHandler].getWorldDirectory
|
||||
|
||||
return new File(
|
||||
DimensionManager.getCurrentSaveRootDirectory,
|
||||
world.provider.getSaveFolder
|
||||
)
|
||||
}
|
||||
|
||||
def saveFile: File = new File(saveDir, "multipart.dat")
|
||||
|
||||
override def load() {
|
||||
try {
|
||||
val in = new FileInputStream(saveFile)
|
||||
loadTag(CompressedStreamTools.readCompressed(in))
|
||||
in.close()
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
}
|
||||
|
||||
loadTag(new NBTTagCompound)
|
||||
}
|
||||
|
||||
def loadTag(tag: NBTTagCompound) {
|
||||
if (tag.hasKey("schedTime"))
|
||||
schedTime = tag.getLong("schedTime")
|
||||
else
|
||||
schedTime = world.getTotalWorldTime
|
||||
}
|
||||
|
||||
def saveTag: NBTTagCompound = {
|
||||
val tag = new NBTTagCompound
|
||||
tag.setLong("schedTime", schedTime)
|
||||
return tag
|
||||
}
|
||||
|
||||
override def save() {
|
||||
val file = saveFile
|
||||
if (!file.getParentFile.exists)
|
||||
file.getParentFile.mkdirs()
|
||||
if (!file.exists)
|
||||
file.createNewFile()
|
||||
|
||||
val dout = new DataOutputStream(new FileOutputStream(file))
|
||||
CompressedStreamTools.writeCompressed(saveTag, dout)
|
||||
dout.close()
|
||||
}
|
||||
|
||||
def nextRandomTick = world.rand.nextInt(800) + 800
|
||||
}
|
||||
|
||||
def createWorldExtension(world: World): WorldExtension =
|
||||
new WorldTickScheduler(world)
|
||||
|
||||
private class ChunkTickScheduler(chunk$ : Chunk, world: WorldTickScheduler)
|
||||
extends ChunkExtension(chunk$, world) {
|
||||
import codechicken.multipart.handler.MultipartProxy._
|
||||
|
||||
var tickList = ListBuffer[PartTickEntry]()
|
||||
|
||||
def schedTime = world.schedTime
|
||||
|
||||
def scheduleTick(part: TMultiPart, time: Long, random: Boolean) {
|
||||
val it = tickList.iterator
|
||||
while (it.hasNext) {
|
||||
val e = it.next()
|
||||
if (e.part == part) {
|
||||
if (
|
||||
e.random && !random
|
||||
) // only override an existing tick if we're going from random->scheduled
|
||||
{
|
||||
e.time = time
|
||||
e.random = random
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
tickList += new PartTickEntry(part, time, random)
|
||||
if (tickList.size == 1)
|
||||
world.tickChunks += this
|
||||
}
|
||||
|
||||
def nextRandomTick = world.nextRandomTick
|
||||
|
||||
def processTicks(): Boolean = {
|
||||
tickList = tickList.filter(processTick)
|
||||
return !tickList.isEmpty
|
||||
}
|
||||
|
||||
def processTick(e: PartTickEntry): Boolean = {
|
||||
if (e.time <= schedTime) {
|
||||
if (e.part.tile != null) {
|
||||
if (e.random && e.part.isInstanceOf[TRandomUpdateTick])
|
||||
e.part.asInstanceOf[TRandomUpdateTick].randomUpdate()
|
||||
else
|
||||
e.part.scheduledTick()
|
||||
|
||||
if (e.part.isInstanceOf[TRandomUpdateTick]) {
|
||||
e.time = schedTime + nextRandomTick
|
||||
e.random = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override def saveData(data: NBTTagCompound) {
|
||||
val tagList = new NBTTagList
|
||||
tickList.foreach { e =>
|
||||
val part = e.part
|
||||
if (part.tile != null && !e.random) {
|
||||
val tag = new NBTTagCompound
|
||||
tag.setShort("pos", indexInChunk(new BlockCoord(part.tile)).toShort)
|
||||
tag.setByte("i", part.tile.partList.indexOf(part).toByte)
|
||||
tag.setLong("time", e.time)
|
||||
tagList.appendTag(tag)
|
||||
}
|
||||
}
|
||||
if (tagList.tagCount > 0)
|
||||
data.setTag("multipartTicks", tagList)
|
||||
}
|
||||
|
||||
override def loadData(data: NBTTagCompound) {
|
||||
tickList.clear()
|
||||
if (!data.hasKey("multipartTicks"))
|
||||
return
|
||||
|
||||
val tagList = data.getTagList("multipartTicks", 10)
|
||||
val cc = new ChunkCoordIntPair(0, 0)
|
||||
for (i <- 0 until tagList.tagCount) {
|
||||
val tag = tagList.getCompoundTagAt(i)
|
||||
val pos = indexInChunk(cc, tag.getShort("pos"))
|
||||
val tile =
|
||||
chunk.chunkTileEntityMap.get(new ChunkPosition(pos.x, pos.y, pos.z))
|
||||
if (tile.isInstanceOf[TileMultipart])
|
||||
tickList += new PartTickEntry(
|
||||
tile.asInstanceOf[TileMultipart].partList.apply(tag.getByte("i")),
|
||||
tag.getLong("time"),
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override def unload() {
|
||||
if (!tickList.isEmpty)
|
||||
world.tickChunks -= this
|
||||
}
|
||||
}
|
||||
|
||||
def createChunkExtension(
|
||||
chunk: Chunk,
|
||||
world: WorldExtension
|
||||
): ChunkExtension =
|
||||
new ChunkTickScheduler(chunk, world.asInstanceOf[WorldTickScheduler])
|
||||
|
||||
/** Start random ticking for a part. Should be called from
|
||||
* TMultiPart.onWorldJoin
|
||||
*/
|
||||
def loadRandomTick(part: TRandomUpdateTick) {
|
||||
getExtension(part.tile.getWorldObj)
|
||||
.asInstanceOf[WorldTickScheduler]
|
||||
.loadRandom(part)
|
||||
}
|
||||
|
||||
/** Schedule a tick for part relative to the current time.
|
||||
*/
|
||||
def scheduleTick(part: TMultiPart, ticks: Int) {
|
||||
getExtension(part.tile.getWorldObj)
|
||||
.asInstanceOf[WorldTickScheduler]
|
||||
.scheduleTick(part, ticks, false)
|
||||
}
|
||||
|
||||
/** Returns the current scheduler time. Like the world time, but unaffected by
|
||||
* the time set command and other things changing time of day. Deprecated in
|
||||
* favor of world.getTotalWorldTime
|
||||
*/
|
||||
@Deprecated
|
||||
def getSchedulerTime(world: World): Long =
|
||||
getExtension(world).asInstanceOf[WorldTickScheduler].schedTime
|
||||
}
|
||||
@@ -86,7 +86,7 @@ class MultipartProxy_serverImpl {
|
||||
MultipartSPH
|
||||
)
|
||||
|
||||
WorldExtensionManager.registerWorldExtension(TickScheduler)
|
||||
WorldExtensionManager.registerWorldExtension(TickScheduler$.MODULE$)
|
||||
TileChunkLoadHook.init()
|
||||
|
||||
MultipartCompatiblity.load()
|
||||
|
||||
Reference in new issue
Block a user