2023-03-15 21:52:13 +00:00
|
|
|
// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: LicenseRef-CCPL
|
|
|
|
|
2017-05-01 13:32:39 +00:00
|
|
|
package dan200.computercraft.shared.peripheral.monitor;
|
|
|
|
|
2022-10-21 18:00:29 +00:00
|
|
|
import com.google.common.annotations.VisibleForTesting;
|
2022-11-09 23:58:56 +00:00
|
|
|
import dan200.computercraft.annotations.ForgeOverride;
|
2017-05-01 13:32:39 +00:00
|
|
|
import dan200.computercraft.api.peripheral.IComputerAccess;
|
|
|
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
|
|
|
import dan200.computercraft.core.terminal.Terminal;
|
2022-10-30 09:20:26 +00:00
|
|
|
import dan200.computercraft.shared.computer.terminal.TerminalState;
|
2022-11-09 20:10:24 +00:00
|
|
|
import dan200.computercraft.shared.config.Config;
|
2022-11-10 15:48:26 +00:00
|
|
|
import dan200.computercraft.shared.util.BlockEntityHelpers;
|
2019-06-21 19:57:38 +00:00
|
|
|
import dan200.computercraft.shared.util.TickScheduler;
|
2021-08-03 20:46:53 +00:00
|
|
|
import net.minecraft.core.BlockPos;
|
|
|
|
import net.minecraft.core.Direction;
|
|
|
|
import net.minecraft.nbt.CompoundTag;
|
2021-11-30 22:37:07 +00:00
|
|
|
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
|
2022-11-10 15:48:26 +00:00
|
|
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
2021-08-03 20:46:53 +00:00
|
|
|
import net.minecraft.world.level.block.entity.BlockEntityType;
|
|
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
|
|
import net.minecraft.world.phys.AABB;
|
2022-11-09 20:10:24 +00:00
|
|
|
import org.slf4j.Logger;
|
|
|
|
import org.slf4j.LoggerFactory;
|
2017-05-01 13:32:39 +00:00
|
|
|
|
2020-05-15 16:09:12 +00:00
|
|
|
import javax.annotation.Nullable;
|
2023-08-23 08:56:21 +00:00
|
|
|
import java.util.Collections;
|
2017-05-01 13:32:39 +00:00
|
|
|
import java.util.Set;
|
2023-08-23 08:56:21 +00:00
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
2021-11-24 13:35:57 +00:00
|
|
|
import java.util.function.Consumer;
|
2017-05-01 13:32:39 +00:00
|
|
|
|
2022-11-10 15:48:26 +00:00
|
|
|
public class MonitorBlockEntity extends BlockEntity {
|
2022-11-09 23:58:56 +00:00
|
|
|
private static final Logger LOG = LoggerFactory.getLogger(MonitorBlockEntity.class);
|
2022-11-09 20:10:24 +00:00
|
|
|
|
2019-03-29 21:21:39 +00:00
|
|
|
public static final double RENDER_BORDER = 2.0 / 16.0;
|
|
|
|
public static final double RENDER_MARGIN = 0.5 / 16.0;
|
|
|
|
public static final double RENDER_PIXEL_SCALE = 1.0 / 64.0;
|
2018-12-23 17:46:58 +00:00
|
|
|
|
Update CC: Tweaked to 1.13
Look, I originally had this split into several commits, but lots of
other cleanups got mixed in. I then backported some of the cleanups to
1.12, did other tidy ups there, and eventually the web of merges was
unreadable.
Yes, this is a horrible mess, but it's still nicer than it was. Anyway,
changes:
- Flatten everything. For instance, there are now three instances of
BlockComputer, two BlockTurtle, ItemPocketComputer. There's also no
more BlockPeripheral (thank heavens) - there's separate block classes
for each peripheral type.
- Remove pretty much all legacy code. As we're breaking world
compatibility anyway, we can remove all the code to load worlds from
1.4 days.
- The command system is largely rewriten to take advantage of 1.13's
new system. It's very fancy!
- WidgetTerminal now uses Minecraft's "GUI listener" system.
- BREAKING CHANGE: All the codes in keys.lua are different, due to the
move to LWJGL 3. Hopefully this won't have too much of an impact.
I don't want to map to the old key codes on the Java side, as there
always ends up being small but slight inconsistencies. IMO it's
better to make a clean break - people should be using keys rather
than hard coding the constants anyway.
- commands.list now allows fetching sub-commands. The ROM has already
been updated to allow fancy usage such as commands.time.set("noon").
- Turtles, modems and cables can be waterlogged.
2019-04-02 12:27:27 +00:00
|
|
|
private static final String NBT_X = "XIndex";
|
|
|
|
private static final String NBT_Y = "YIndex";
|
|
|
|
private static final String NBT_WIDTH = "Width";
|
|
|
|
private static final String NBT_HEIGHT = "Height";
|
|
|
|
|
|
|
|
private final boolean advanced;
|
|
|
|
|
2022-11-09 18:58:31 +00:00
|
|
|
private @Nullable ServerMonitor serverMonitor;
|
Don't cache the client monitor
When rendering non-origin monitors, we would fetch the origin monitor,
read its client state, and then cache that on the current monitor to
avoid repeated lookups.
However, if the origin monitor is unloaded/removed on the client, and
then loaded agin, this cache will be not be invalidated, causing us to
render both the old and new monitor!
I think the correct thing to do here is cache the origin monitor. This
allows us to check when the origin monitor has been removed, and
invalidate the cache if needed.
However, I'm wary of any other edge cases here, so for now we do
something much simpler, and remove the cache entirely. This does mean
that monitors now need to perform extra block entity lookups, but the
performance cost doesn't appear to be too bad.
Fixes #1741
2024-03-10 10:57:56 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The monitor's state on the client. This is defined iff we're the origin monitor
|
|
|
|
* ({@code xIndex == 0 && yIndex == 0}).
|
|
|
|
*/
|
2022-11-09 18:58:31 +00:00
|
|
|
private @Nullable ClientMonitor clientMonitor;
|
Don't cache the client monitor
When rendering non-origin monitors, we would fetch the origin monitor,
read its client state, and then cache that on the current monitor to
avoid repeated lookups.
However, if the origin monitor is unloaded/removed on the client, and
then loaded agin, this cache will be not be invalidated, causing us to
render both the old and new monitor!
I think the correct thing to do here is cache the origin monitor. This
allows us to check when the origin monitor has been removed, and
invalidate the cache if needed.
However, I'm wary of any other edge cases here, so for now we do
something much simpler, and remove the cache entirely. This does mean
that monitors now need to perform extra block entity lookups, but the
performance cost doesn't appear to be too bad.
Fixes #1741
2024-03-10 10:57:56 +00:00
|
|
|
|
2022-11-09 18:58:31 +00:00
|
|
|
private @Nullable MonitorPeripheral peripheral;
|
2023-08-23 08:56:21 +00:00
|
|
|
private final Set<IComputerAccess> computers = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
2017-05-01 13:32:39 +00:00
|
|
|
|
2021-05-04 21:24:58 +00:00
|
|
|
private boolean needsUpdate = false;
|
2021-07-25 13:18:07 +00:00
|
|
|
private boolean needsValidating = false;
|
2017-05-01 13:32:39 +00:00
|
|
|
|
2020-05-20 07:44:44 +00:00
|
|
|
// MonitorWatcher state.
|
|
|
|
boolean enqueued;
|
2022-11-09 18:58:31 +00:00
|
|
|
@Nullable
|
2020-05-20 07:44:44 +00:00
|
|
|
TerminalState cached;
|
|
|
|
|
2021-01-15 16:35:49 +00:00
|
|
|
private int width = 1;
|
|
|
|
private int height = 1;
|
|
|
|
private int xIndex = 0;
|
|
|
|
private int yIndex = 0;
|
2017-05-01 13:32:39 +00:00
|
|
|
|
2022-11-09 18:58:31 +00:00
|
|
|
private @Nullable BlockPos bbPos;
|
|
|
|
private @Nullable BlockState bbState;
|
Cleanup and optimise terminal rendering (#1057)
- Remove the POSITION_COLOR render type. Instead we just render a
background terminal quad as the pocket computer light - it's a little
(lot?) more cheaty, but saves having to create a render type.
- Use the existing position_color_tex shader instead of our copy. I
looked at using RenderType.text, but had a bunch of problems with GUI
terminals. Its possible we can fix it, but didn't want to spend too
much time on it.
- Remove some methods from FixedWidthFontRenderer, inlining them into
the call site.
- Switch back to using GL_QUADS rather than GL_TRIANGLES. I know Lig
will shout at me for this, but the rest of MC uses QUADS, so I don't
think best practice really matters here.
- Fix the TBO backend monitor not rendering monitors with fog.
Unfortunately we can't easily do this to the VBO one without writing
a custom shader (which defeats the whole point of the VBO backend!),
as the distance calculation of most render types expect an
already-transformed position (camera-relative I think!) while we pass
a world-relative one.
- When rendering to a VBO we push vertices to a ByteBuffer directly,
rather than going through MC's VertexConsumer system. This removes
the overhead which comes with VertexConsumer, significantly improving
performance.
- Pre-convert palette colours to bytes, storing both the coloured and
greyscale versions as a byte array. This allows us to remove the
multiple casts and conversions (double -> float -> (greyscale) ->
byte), offering noticeable performance improvements (multiple ms per
frame).
We're using a byte[] here rather than a record of three bytes as
notionally it provides better performance when writing to a
ByteBuffer directly compared to calling .put() four times. [^1]
- Memorize getRenderBoundingBox. This was taking about 5% of the total
time on the render thread[^2], so worth doing.
I don't actually think the allocation is the heavy thing here -
VisualVM says it's toWorldPos being slow. I'm not sure why - possibly
just all the block property lookups? [^2]
Note that none of these changes improve compatibility with Optifine.
Right now there's some serious issues where monitors are writing _over_
blocks in front of them. To fix this, we probably need to remove the
depth blocker and just render characters with a z offset. Will do that
in a separate commit, as I need to evaluate how well that change will
work first.
The main advantage of this commit is the improved performance. In my
stress test with 120 monitors updating every tick, I'm getting 10-20fps
[^3] (still much worse than TBOs, which manages a solid 60-100).
In practice, we'll actually be much better than this. Our network
bandwidth limits means only 40 change in a single tick - and so FPS is
much more reasonable (+60fps).
[^1]: In general, put(byte[]) is faster than put(byte) multiple times.
Just not clear if this is true when dealing with a small (and loop
unrolled) number of bytes.
[^2]: To be clear, this is with 120 monitors and no other block entities
with custom renderers. so not really representative.
[^3]: I wish I could provide a narrower range, but it varies so much
between me restarting the game. Makes it impossible to benchmark
anything!
2022-04-02 09:54:03 +00:00
|
|
|
private int bbX, bbY, bbWidth, bbHeight;
|
2022-11-09 18:58:31 +00:00
|
|
|
private @Nullable AABB boundingBox;
|
Cleanup and optimise terminal rendering (#1057)
- Remove the POSITION_COLOR render type. Instead we just render a
background terminal quad as the pocket computer light - it's a little
(lot?) more cheaty, but saves having to create a render type.
- Use the existing position_color_tex shader instead of our copy. I
looked at using RenderType.text, but had a bunch of problems with GUI
terminals. Its possible we can fix it, but didn't want to spend too
much time on it.
- Remove some methods from FixedWidthFontRenderer, inlining them into
the call site.
- Switch back to using GL_QUADS rather than GL_TRIANGLES. I know Lig
will shout at me for this, but the rest of MC uses QUADS, so I don't
think best practice really matters here.
- Fix the TBO backend monitor not rendering monitors with fog.
Unfortunately we can't easily do this to the VBO one without writing
a custom shader (which defeats the whole point of the VBO backend!),
as the distance calculation of most render types expect an
already-transformed position (camera-relative I think!) while we pass
a world-relative one.
- When rendering to a VBO we push vertices to a ByteBuffer directly,
rather than going through MC's VertexConsumer system. This removes
the overhead which comes with VertexConsumer, significantly improving
performance.
- Pre-convert palette colours to bytes, storing both the coloured and
greyscale versions as a byte array. This allows us to remove the
multiple casts and conversions (double -> float -> (greyscale) ->
byte), offering noticeable performance improvements (multiple ms per
frame).
We're using a byte[] here rather than a record of three bytes as
notionally it provides better performance when writing to a
ByteBuffer directly compared to calling .put() four times. [^1]
- Memorize getRenderBoundingBox. This was taking about 5% of the total
time on the render thread[^2], so worth doing.
I don't actually think the allocation is the heavy thing here -
VisualVM says it's toWorldPos being slow. I'm not sure why - possibly
just all the block property lookups? [^2]
Note that none of these changes improve compatibility with Optifine.
Right now there's some serious issues where monitors are writing _over_
blocks in front of them. To fix this, we probably need to remove the
depth blocker and just render characters with a z offset. Will do that
in a separate commit, as I need to evaluate how well that change will
work first.
The main advantage of this commit is the improved performance. In my
stress test with 120 monitors updating every tick, I'm getting 10-20fps
[^3] (still much worse than TBOs, which manages a solid 60-100).
In practice, we'll actually be much better than this. Our network
bandwidth limits means only 40 change in a single tick - and so FPS is
much more reasonable (+60fps).
[^1]: In general, put(byte[]) is faster than put(byte) multiple times.
Just not clear if this is true when dealing with a small (and loop
unrolled) number of bytes.
[^2]: To be clear, this is with 120 monitors and no other block entities
with custom renderers. so not really representative.
[^3]: I wish I could provide a narrower range, but it varies so much
between me restarting the game. Makes it impossible to benchmark
anything!
2022-04-02 09:54:03 +00:00
|
|
|
|
2022-10-28 22:40:55 +00:00
|
|
|
TickScheduler.Token tickToken = new TickScheduler.Token(this);
|
|
|
|
|
2022-11-09 23:58:56 +00:00
|
|
|
public MonitorBlockEntity(BlockEntityType<? extends MonitorBlockEntity> type, BlockPos pos, BlockState state, boolean advanced) {
|
2021-08-03 20:46:53 +00:00
|
|
|
super(type, pos, state);
|
Update CC: Tweaked to 1.13
Look, I originally had this split into several commits, but lots of
other cleanups got mixed in. I then backported some of the cleanups to
1.12, did other tidy ups there, and eventually the web of merges was
unreadable.
Yes, this is a horrible mess, but it's still nicer than it was. Anyway,
changes:
- Flatten everything. For instance, there are now three instances of
BlockComputer, two BlockTurtle, ItemPocketComputer. There's also no
more BlockPeripheral (thank heavens) - there's separate block classes
for each peripheral type.
- Remove pretty much all legacy code. As we're breaking world
compatibility anyway, we can remove all the code to load worlds from
1.4 days.
- The command system is largely rewriten to take advantage of 1.13's
new system. It's very fancy!
- WidgetTerminal now uses Minecraft's "GUI listener" system.
- BREAKING CHANGE: All the codes in keys.lua are different, due to the
move to LWJGL 3. Hopefully this won't have too much of an impact.
I don't want to map to the old key codes on the Java side, as there
always ends up being small but slight inconsistencies. IMO it's
better to make a clean break - people should be using keys rather
than hard coding the constants anyway.
- commands.list now allows fetching sub-commands. The ROM has already
been updated to allow fancy usage such as commands.time.set("noon").
- Turtles, modems and cables can be waterlogged.
2019-04-02 12:27:27 +00:00
|
|
|
this.advanced = advanced;
|
|
|
|
}
|
2017-05-01 13:32:39 +00:00
|
|
|
|
2018-02-14 21:21:00 +00:00
|
|
|
@Override
|
2022-11-10 15:48:26 +00:00
|
|
|
public void clearRemoved() {
|
2021-08-03 20:46:53 +00:00
|
|
|
super.clearRemoved();
|
2021-11-24 13:35:57 +00:00
|
|
|
needsValidating = true; // Same, tbh
|
2022-10-28 22:40:55 +00:00
|
|
|
TickScheduler.schedule(tickToken);
|
2018-02-14 21:21:00 +00:00
|
|
|
}
|
|
|
|
|
2022-11-10 15:48:26 +00:00
|
|
|
void destroy() {
|
Update CC: Tweaked to 1.13
Look, I originally had this split into several commits, but lots of
other cleanups got mixed in. I then backported some of the cleanups to
1.12, did other tidy ups there, and eventually the web of merges was
unreadable.
Yes, this is a horrible mess, but it's still nicer than it was. Anyway,
changes:
- Flatten everything. For instance, there are now three instances of
BlockComputer, two BlockTurtle, ItemPocketComputer. There's also no
more BlockPeripheral (thank heavens) - there's separate block classes
for each peripheral type.
- Remove pretty much all legacy code. As we're breaking world
compatibility anyway, we can remove all the code to load worlds from
1.4 days.
- The command system is largely rewriten to take advantage of 1.13's
new system. It's very fancy!
- WidgetTerminal now uses Minecraft's "GUI listener" system.
- BREAKING CHANGE: All the codes in keys.lua are different, due to the
move to LWJGL 3. Hopefully this won't have too much of an impact.
I don't want to map to the old key codes on the Java side, as there
always ends up being small but slight inconsistencies. IMO it's
better to make a clean break - people should be using keys rather
than hard coding the constants anyway.
- commands.list now allows fetching sub-commands. The ROM has already
been updated to allow fancy usage such as commands.time.set("noon").
- Turtles, modems and cables can be waterlogged.
2019-04-02 12:27:27 +00:00
|
|
|
// TODO: Call this before using the block
|
2021-01-09 19:22:58 +00:00
|
|
|
if (!getLevel().isClientSide) contractNeighbours();
|
2018-02-14 21:21:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-01-09 19:22:58 +00:00
|
|
|
public void setRemoved() {
|
|
|
|
super.setRemoved();
|
Don't cache the client monitor
When rendering non-origin monitors, we would fetch the origin monitor,
read its client state, and then cache that on the current monitor to
avoid repeated lookups.
However, if the origin monitor is unloaded/removed on the client, and
then loaded agin, this cache will be not be invalidated, causing us to
render both the old and new monitor!
I think the correct thing to do here is cache the origin monitor. This
allows us to check when the origin monitor has been removed, and
invalidate the cache if needed.
However, I'm wary of any other edge cases here, so for now we do
something much simpler, and remove the cache entirely. This does mean
that monitors now need to perform extra block entity lookups, but the
performance cost doesn't appear to be too bad.
Fixes #1741
2024-03-10 10:57:56 +00:00
|
|
|
if (clientMonitor != null) clientMonitor.destroy();
|
2018-02-14 21:21:00 +00:00
|
|
|
}
|
|
|
|
|
2017-05-01 13:32:39 +00:00
|
|
|
@Override
|
2021-12-01 23:16:54 +00:00
|
|
|
public void saveAdditional(CompoundTag tag) {
|
2021-01-15 16:35:49 +00:00
|
|
|
tag.putInt(NBT_X, xIndex);
|
|
|
|
tag.putInt(NBT_Y, yIndex);
|
|
|
|
tag.putInt(NBT_WIDTH, width);
|
|
|
|
tag.putInt(NBT_HEIGHT, height);
|
2021-12-01 23:16:54 +00:00
|
|
|
super.saveAdditional(tag);
|
2017-05-01 13:32:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2022-11-09 18:58:31 +00:00
|
|
|
public void load(CompoundTag nbt) {
|
2021-08-03 20:46:53 +00:00
|
|
|
super.load(nbt);
|
2020-07-11 19:36:10 +00:00
|
|
|
|
2022-11-10 15:48:26 +00:00
|
|
|
var oldXIndex = xIndex;
|
|
|
|
var oldYIndex = yIndex;
|
|
|
|
|
2021-01-16 11:38:59 +00:00
|
|
|
xIndex = nbt.getInt(NBT_X);
|
|
|
|
yIndex = nbt.getInt(NBT_Y);
|
|
|
|
width = nbt.getInt(NBT_WIDTH);
|
|
|
|
height = nbt.getInt(NBT_HEIGHT);
|
2022-11-10 15:48:26 +00:00
|
|
|
|
|
|
|
if (level != null && level.isClientSide) onClientLoad(oldXIndex, oldYIndex);
|
2017-05-01 13:32:39 +00:00
|
|
|
}
|
|
|
|
|
2022-11-10 15:48:26 +00:00
|
|
|
void blockTick() {
|
2021-07-25 13:18:07 +00:00
|
|
|
if (needsValidating) {
|
|
|
|
needsValidating = false;
|
|
|
|
validate();
|
|
|
|
}
|
|
|
|
|
2021-06-01 17:55:12 +00:00
|
|
|
if (needsUpdate) {
|
2021-05-04 21:24:58 +00:00
|
|
|
needsUpdate = false;
|
2021-11-24 13:35:57 +00:00
|
|
|
expand();
|
2021-05-04 21:24:58 +00:00
|
|
|
}
|
|
|
|
|
2021-01-15 16:35:49 +00:00
|
|
|
if (xIndex != 0 || yIndex != 0 || serverMonitor == null) return;
|
2017-05-01 13:32:39 +00:00
|
|
|
|
2021-11-24 13:35:57 +00:00
|
|
|
if (serverMonitor.pollResized()) eachComputer(c -> c.queueEvent("monitor_resize", c.getAttachmentName()));
|
2021-01-15 16:35:49 +00:00
|
|
|
if (serverMonitor.pollTerminalChanged()) MonitorWatcher.enqueue(this);
|
2017-05-01 14:48:44 +00:00
|
|
|
}
|
2017-05-01 13:32:39 +00:00
|
|
|
|
2021-11-24 13:35:57 +00:00
|
|
|
@Nullable
|
2022-10-21 18:00:29 +00:00
|
|
|
@VisibleForTesting
|
2018-02-14 21:21:00 +00:00
|
|
|
public ServerMonitor getCachedServerMonitor() {
|
2021-01-15 16:35:49 +00:00
|
|
|
return serverMonitor;
|
2018-02-14 21:21:00 +00:00
|
|
|
}
|
|
|
|
|
2021-11-24 13:35:57 +00:00
|
|
|
@Nullable
|
2018-02-14 21:21:00 +00:00
|
|
|
private ServerMonitor getServerMonitor() {
|
2021-01-15 16:35:49 +00:00
|
|
|
if (serverMonitor != null) return serverMonitor;
|
2018-02-14 21:21:00 +00:00
|
|
|
|
Don't cache the client monitor
When rendering non-origin monitors, we would fetch the origin monitor,
read its client state, and then cache that on the current monitor to
avoid repeated lookups.
However, if the origin monitor is unloaded/removed on the client, and
then loaded agin, this cache will be not be invalidated, causing us to
render both the old and new monitor!
I think the correct thing to do here is cache the origin monitor. This
allows us to check when the origin monitor has been removed, and
invalidate the cache if needed.
However, I'm wary of any other edge cases here, so for now we do
something much simpler, and remove the cache entirely. This does mean
that monitors now need to perform extra block entity lookups, but the
performance cost doesn't appear to be too bad.
Fixes #1741
2024-03-10 10:57:56 +00:00
|
|
|
var origin = getOrigin();
|
2018-02-14 21:21:00 +00:00
|
|
|
if (origin == null) return null;
|
|
|
|
|
2021-01-15 16:35:49 +00:00
|
|
|
return serverMonitor = origin.serverMonitor;
|
2018-02-14 21:21:00 +00:00
|
|
|
}
|
|
|
|
|
2021-11-24 13:35:57 +00:00
|
|
|
@Nullable
|
2018-02-14 21:21:00 +00:00
|
|
|
private ServerMonitor createServerMonitor() {
|
2021-01-15 16:35:49 +00:00
|
|
|
if (serverMonitor != null) return serverMonitor;
|
2019-04-02 10:50:13 +00:00
|
|
|
|
2021-01-15 16:35:49 +00:00
|
|
|
if (xIndex == 0 && yIndex == 0) {
|
2018-02-14 21:21:00 +00:00
|
|
|
// If we're the origin, set up the new monitor
|
2021-01-15 16:35:49 +00:00
|
|
|
serverMonitor = new ServerMonitor(advanced, this);
|
2018-02-14 21:21:00 +00:00
|
|
|
|
|
|
|
// And propagate it to child monitors
|
2021-01-15 16:35:49 +00:00
|
|
|
for (var x = 0; x < width; x++) {
|
|
|
|
for (var y = 0; y < height; y++) {
|
2021-11-24 13:35:57 +00:00
|
|
|
var monitor = getLoadedMonitor(x, y).getMonitor();
|
2021-01-15 16:35:49 +00:00
|
|
|
if (monitor != null) monitor.serverMonitor = serverMonitor;
|
2017-05-01 13:32:39 +00:00
|
|
|
}
|
|
|
|
}
|
2018-02-14 21:21:00 +00:00
|
|
|
|
2021-01-15 16:35:49 +00:00
|
|
|
return serverMonitor;
|
2018-02-14 21:21:00 +00:00
|
|
|
} else {
|
|
|
|
// Otherwise fetch the origin and attempt to get its monitor
|
|
|
|
// Note this may load chunks, but we don't really have a choice here.
|
2024-04-06 07:38:44 +00:00
|
|
|
var te = getLevel().getBlockEntity(toWorldPos(0, 0));
|
2022-11-09 23:58:56 +00:00
|
|
|
if (!(te instanceof MonitorBlockEntity monitor)) return null;
|
2018-02-14 21:21:00 +00:00
|
|
|
|
2022-10-25 21:58:31 +00:00
|
|
|
return serverMonitor = monitor.createServerMonitor();
|
2018-02-14 21:21:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Ensure the terminal exists when creating a monitor peripheral
Previously we had the invariant that if we had a server monitor, we also
had a terminal. When a monitor shrank into a place, we deleted the
monitor, and then recreated it when a peripheral was requested.
As of ab785a090698e6972caac95691393428c6f8370b this has changed
slightly, and we now just delete the terminal (keeping the ServerMonitor
around). However, we didn't adjust the peripheral code accordingly,
meaning we didn't recreate the /terminal/ when a peripheral was
requested.
The fix for this is very simple - most of the rest of this commit is
some additional code for ensuring monitor invariants hold, so we can
write tests with a little more confidence.
I'm not 100% sold on this approach. It's tricky having a double layer of
nullable state (ServerMonitor, and then the terminal). However, I think
this is reasonable - the ServerMonitor is a reference to the multiblock,
and the Terminal is part of the multiblock's state.
Even after all the refactors, monitor code is still nastier than I'd
like :/.
Fixes #1608
2023-10-09 21:03:43 +00:00
|
|
|
private void createServerTerminal() {
|
|
|
|
var monitor = createServerMonitor();
|
|
|
|
if (monitor != null && monitor.getTerminal() == null) monitor.rebuild();
|
|
|
|
}
|
|
|
|
|
2021-11-24 13:35:57 +00:00
|
|
|
@Nullable
|
Don't cache the client monitor
When rendering non-origin monitors, we would fetch the origin monitor,
read its client state, and then cache that on the current monitor to
avoid repeated lookups.
However, if the origin monitor is unloaded/removed on the client, and
then loaded agin, this cache will be not be invalidated, causing us to
render both the old and new monitor!
I think the correct thing to do here is cache the origin monitor. This
allows us to check when the origin monitor has been removed, and
invalidate the cache if needed.
However, I'm wary of any other edge cases here, so for now we do
something much simpler, and remove the cache entirely. This does mean
that monitors now need to perform extra block entity lookups, but the
performance cost doesn't appear to be too bad.
Fixes #1741
2024-03-10 10:57:56 +00:00
|
|
|
public ClientMonitor getOriginClientMonitor() {
|
2021-01-15 16:35:49 +00:00
|
|
|
if (clientMonitor != null) return clientMonitor;
|
2018-02-14 21:21:00 +00:00
|
|
|
|
Don't cache the client monitor
When rendering non-origin monitors, we would fetch the origin monitor,
read its client state, and then cache that on the current monitor to
avoid repeated lookups.
However, if the origin monitor is unloaded/removed on the client, and
then loaded agin, this cache will be not be invalidated, causing us to
render both the old and new monitor!
I think the correct thing to do here is cache the origin monitor. This
allows us to check when the origin monitor has been removed, and
invalidate the cache if needed.
However, I'm wary of any other edge cases here, so for now we do
something much simpler, and remove the cache entirely. This does mean
that monitors now need to perform extra block entity lookups, but the
performance cost doesn't appear to be too bad.
Fixes #1741
2024-03-10 10:57:56 +00:00
|
|
|
var origin = getOrigin();
|
|
|
|
return origin == null ? null : origin.clientMonitor;
|
2017-05-01 13:32:39 +00:00
|
|
|
}
|
|
|
|
|
2017-05-01 14:48:44 +00:00
|
|
|
// Networking stuff
|
2017-05-01 13:32:39 +00:00
|
|
|
|
2021-11-30 22:01:09 +00:00
|
|
|
@Override
|
2021-11-30 22:37:07 +00:00
|
|
|
public final ClientboundBlockEntityDataPacket getUpdatePacket() {
|
2021-11-30 22:48:37 +00:00
|
|
|
return ClientboundBlockEntityDataPacket.create(this);
|
2021-11-30 22:01:09 +00:00
|
|
|
}
|
|
|
|
|
2017-05-01 13:32:39 +00:00
|
|
|
@Override
|
2021-11-30 22:37:07 +00:00
|
|
|
public final CompoundTag getUpdateTag() {
|
|
|
|
var nbt = super.getUpdateTag();
|
2021-01-15 16:35:49 +00:00
|
|
|
nbt.putInt(NBT_X, xIndex);
|
|
|
|
nbt.putInt(NBT_Y, yIndex);
|
|
|
|
nbt.putInt(NBT_WIDTH, width);
|
|
|
|
nbt.putInt(NBT_HEIGHT, height);
|
2021-11-30 22:01:09 +00:00
|
|
|
return nbt;
|
2017-05-01 13:32:39 +00:00
|
|
|
}
|
|
|
|
|
2022-11-10 15:48:26 +00:00
|
|
|
private void onClientLoad(int oldXIndex, int oldYIndex) {
|
Don't cache the client monitor
When rendering non-origin monitors, we would fetch the origin monitor,
read its client state, and then cache that on the current monitor to
avoid repeated lookups.
However, if the origin monitor is unloaded/removed on the client, and
then loaded agin, this cache will be not be invalidated, causing us to
render both the old and new monitor!
I think the correct thing to do here is cache the origin monitor. This
allows us to check when the origin monitor has been removed, and
invalidate the cache if needed.
However, I'm wary of any other edge cases here, so for now we do
something much simpler, and remove the cache entirely. This does mean
that monitors now need to perform extra block entity lookups, but the
performance cost doesn't appear to be too bad.
Fixes #1741
2024-03-10 10:57:56 +00:00
|
|
|
if ((oldXIndex != xIndex || oldYIndex != yIndex) && clientMonitor != null) {
|
|
|
|
// If our index has changed, and we were the origin, then destroy the current monitor.
|
|
|
|
clientMonitor.destroy();
|
2021-01-15 16:35:49 +00:00
|
|
|
clientMonitor = null;
|
2017-05-01 13:32:39 +00:00
|
|
|
}
|
|
|
|
|
Don't cache the client monitor
When rendering non-origin monitors, we would fetch the origin monitor,
read its client state, and then cache that on the current monitor to
avoid repeated lookups.
However, if the origin monitor is unloaded/removed on the client, and
then loaded agin, this cache will be not be invalidated, causing us to
render both the old and new monitor!
I think the correct thing to do here is cache the origin monitor. This
allows us to check when the origin monitor has been removed, and
invalidate the cache if needed.
However, I'm wary of any other edge cases here, so for now we do
something much simpler, and remove the cache entirely. This does mean
that monitors now need to perform extra block entity lookups, but the
performance cost doesn't appear to be too bad.
Fixes #1741
2024-03-10 10:57:56 +00:00
|
|
|
// If we're the origin terminal then create it.
|
|
|
|
if (xIndex == 0 && yIndex == 0 && clientMonitor == null) clientMonitor = new ClientMonitor(this);
|
2017-05-01 13:32:39 +00:00
|
|
|
}
|
|
|
|
|
2024-04-25 17:19:34 +00:00
|
|
|
public final void read(@Nullable TerminalState state) {
|
2021-01-15 16:35:49 +00:00
|
|
|
if (xIndex != 0 || yIndex != 0) {
|
2022-11-09 20:10:24 +00:00
|
|
|
LOG.warn("Receiving monitor state for non-origin terminal at {}", getBlockPos());
|
2020-05-20 07:44:44 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-10-21 18:00:29 +00:00
|
|
|
if (clientMonitor == null) clientMonitor = new ClientMonitor(this);
|
2021-01-15 16:35:49 +00:00
|
|
|
clientMonitor.read(state);
|
2020-05-20 07:44:44 +00:00
|
|
|
}
|
|
|
|
|
2017-05-01 13:32:39 +00:00
|
|
|
// Sizing and placement stuff
|
|
|
|
|
Update CC: Tweaked to 1.13
Look, I originally had this split into several commits, but lots of
other cleanups got mixed in. I then backported some of the cleanups to
1.12, did other tidy ups there, and eventually the web of merges was
unreadable.
Yes, this is a horrible mess, but it's still nicer than it was. Anyway,
changes:
- Flatten everything. For instance, there are now three instances of
BlockComputer, two BlockTurtle, ItemPocketComputer. There's also no
more BlockPeripheral (thank heavens) - there's separate block classes
for each peripheral type.
- Remove pretty much all legacy code. As we're breaking world
compatibility anyway, we can remove all the code to load worlds from
1.4 days.
- The command system is largely rewriten to take advantage of 1.13's
new system. It's very fancy!
- WidgetTerminal now uses Minecraft's "GUI listener" system.
- BREAKING CHANGE: All the codes in keys.lua are different, due to the
move to LWJGL 3. Hopefully this won't have too much of an impact.
I don't want to map to the old key codes on the Java side, as there
always ends up being small but slight inconsistencies. IMO it's
better to make a clean break - people should be using keys rather
than hard coding the constants anyway.
- commands.list now allows fetching sub-commands. The ROM has already
been updated to allow fancy usage such as commands.time.set("noon").
- Turtles, modems and cables can be waterlogged.
2019-04-02 12:27:27 +00:00
|
|
|
private void updateBlockState() {
|
2021-01-09 19:22:58 +00:00
|
|
|
getLevel().setBlock(getBlockPos(), getBlockState()
|
2022-11-09 23:58:56 +00:00
|
|
|
.setValue(MonitorBlock.STATE, MonitorEdgeState.fromConnections(
|
2021-01-15 16:35:49 +00:00
|
|
|
yIndex < height - 1, yIndex > 0,
|
|
|
|
xIndex > 0, xIndex < width - 1)), 2);
|
2017-05-01 13:32:39 +00:00
|
|
|
}
|
|
|
|
|
Update CC: Tweaked to 1.13
Look, I originally had this split into several commits, but lots of
other cleanups got mixed in. I then backported some of the cleanups to
1.12, did other tidy ups there, and eventually the web of merges was
unreadable.
Yes, this is a horrible mess, but it's still nicer than it was. Anyway,
changes:
- Flatten everything. For instance, there are now three instances of
BlockComputer, two BlockTurtle, ItemPocketComputer. There's also no
more BlockPeripheral (thank heavens) - there's separate block classes
for each peripheral type.
- Remove pretty much all legacy code. As we're breaking world
compatibility anyway, we can remove all the code to load worlds from
1.4 days.
- The command system is largely rewriten to take advantage of 1.13's
new system. It's very fancy!
- WidgetTerminal now uses Minecraft's "GUI listener" system.
- BREAKING CHANGE: All the codes in keys.lua are different, due to the
move to LWJGL 3. Hopefully this won't have too much of an impact.
I don't want to map to the old key codes on the Java side, as there
always ends up being small but slight inconsistencies. IMO it's
better to make a clean break - people should be using keys rather
than hard coding the constants anyway.
- commands.list now allows fetching sub-commands. The ROM has already
been updated to allow fancy usage such as commands.time.set("noon").
- Turtles, modems and cables can be waterlogged.
2019-04-02 12:27:27 +00:00
|
|
|
// region Sizing and placement stuff
|
2019-06-08 12:36:31 +00:00
|
|
|
public Direction getDirection() {
|
2020-12-10 19:13:49 +00:00
|
|
|
// Ensure we're actually a monitor block. This _should_ always be the case, but sometimes there's
|
|
|
|
// fun problems with the block being missing on the client.
|
|
|
|
var state = getBlockState();
|
2022-11-09 23:58:56 +00:00
|
|
|
return state.hasProperty(MonitorBlock.FACING) ? state.getValue(MonitorBlock.FACING) : Direction.NORTH;
|
2017-05-01 13:32:39 +00:00
|
|
|
}
|
2018-12-23 17:46:58 +00:00
|
|
|
|
2019-06-08 12:36:31 +00:00
|
|
|
public Direction getOrientation() {
|
2020-12-10 19:13:49 +00:00
|
|
|
var state = getBlockState();
|
2022-11-09 23:58:56 +00:00
|
|
|
return state.hasProperty(MonitorBlock.ORIENTATION) ? state.getValue(MonitorBlock.ORIENTATION) : Direction.NORTH;
|
2017-05-01 13:32:39 +00:00
|
|
|
}
|
|
|
|
|
2019-06-08 12:36:31 +00:00
|
|
|
public Direction getFront() {
|
|
|
|
var orientation = getOrientation();
|
|
|
|
return orientation == Direction.NORTH ? getDirection() : orientation;
|
2017-05-01 13:32:39 +00:00
|
|
|
}
|
|
|
|
|
2019-06-08 12:36:31 +00:00
|
|
|
public Direction getRight() {
|
2021-01-09 19:22:58 +00:00
|
|
|
return getDirection().getCounterClockWise();
|
2017-05-01 13:32:39 +00:00
|
|
|
}
|
2018-12-23 17:46:58 +00:00
|
|
|
|
2019-06-08 12:36:31 +00:00
|
|
|
public Direction getDown() {
|
|
|
|
var orientation = getOrientation();
|
|
|
|
if (orientation == Direction.NORTH) return Direction.UP;
|
|
|
|
return orientation == Direction.DOWN ? getDirection() : getDirection().getOpposite();
|
2017-05-01 13:32:39 +00:00
|
|
|
}
|
2018-12-23 17:46:58 +00:00
|
|
|
|
2017-05-01 13:32:39 +00:00
|
|
|
public int getWidth() {
|
2021-01-15 16:35:49 +00:00
|
|
|
return width;
|
2017-05-01 13:32:39 +00:00
|
|
|
}
|
2018-12-23 17:46:58 +00:00
|
|
|
|
2017-05-01 13:32:39 +00:00
|
|
|
public int getHeight() {
|
2021-01-15 16:35:49 +00:00
|
|
|
return height;
|
2017-05-01 13:32:39 +00:00
|
|
|
}
|
2018-12-23 17:46:58 +00:00
|
|
|
|
2017-05-01 13:32:39 +00:00
|
|
|
public int getXIndex() {
|
2021-01-15 16:35:49 +00:00
|
|
|
return xIndex;
|
2017-05-01 13:32:39 +00:00
|
|
|
}
|
2018-12-23 17:46:58 +00:00
|
|
|
|
2017-05-01 13:32:39 +00:00
|
|
|
public int getYIndex() {
|
2021-01-15 16:35:49 +00:00
|
|
|
return yIndex;
|
2017-05-01 13:32:39 +00:00
|
|
|
}
|
2018-12-23 17:46:58 +00:00
|
|
|
|
2022-11-09 23:58:56 +00:00
|
|
|
boolean isCompatible(MonitorBlockEntity other) {
|
Don't cache the client monitor
When rendering non-origin monitors, we would fetch the origin monitor,
read its client state, and then cache that on the current monitor to
avoid repeated lookups.
However, if the origin monitor is unloaded/removed on the client, and
then loaded agin, this cache will be not be invalidated, causing us to
render both the old and new monitor!
I think the correct thing to do here is cache the origin monitor. This
allows us to check when the origin monitor has been removed, and
invalidate the cache if needed.
However, I'm wary of any other edge cases here, so for now we do
something much simpler, and remove the cache entirely. This does mean
that monitors now need to perform extra block entity lookups, but the
performance cost doesn't appear to be too bad.
Fixes #1741
2024-03-10 10:57:56 +00:00
|
|
|
return advanced == other.advanced && getOrientation() == other.getOrientation() && getDirection() == other.getDirection();
|
2021-11-24 13:35:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a tile within the current monitor only if it is loaded and compatible.
|
|
|
|
*
|
|
|
|
* @param x Absolute X position in monitor coordinates
|
|
|
|
* @param y Absolute Y position in monitor coordinates
|
|
|
|
* @return The located monitor
|
|
|
|
*/
|
|
|
|
private MonitorState getLoadedMonitor(int x, int y) {
|
|
|
|
if (x == xIndex && y == yIndex) return MonitorState.present(this);
|
|
|
|
var pos = toWorldPos(x, y);
|
2017-05-01 13:32:39 +00:00
|
|
|
|
2021-08-03 20:46:53 +00:00
|
|
|
var world = getLevel();
|
2021-11-30 22:48:37 +00:00
|
|
|
if (world == null || !world.isLoaded(pos)) return MonitorState.UNLOADED;
|
2018-12-30 16:14:07 +00:00
|
|
|
|
2021-08-03 20:46:53 +00:00
|
|
|
var tile = world.getBlockEntity(pos);
|
2022-11-09 23:58:56 +00:00
|
|
|
if (!(tile instanceof MonitorBlockEntity monitor)) return MonitorState.MISSING;
|
2018-12-30 16:14:07 +00:00
|
|
|
|
2021-11-24 13:35:57 +00:00
|
|
|
return isCompatible(monitor) ? MonitorState.present(monitor) : MonitorState.MISSING;
|
2017-05-01 13:32:39 +00:00
|
|
|
}
|
|
|
|
|
Don't cache the client monitor
When rendering non-origin monitors, we would fetch the origin monitor,
read its client state, and then cache that on the current monitor to
avoid repeated lookups.
However, if the origin monitor is unloaded/removed on the client, and
then loaded agin, this cache will be not be invalidated, causing us to
render both the old and new monitor!
I think the correct thing to do here is cache the origin monitor. This
allows us to check when the origin monitor has been removed, and
invalidate the cache if needed.
However, I'm wary of any other edge cases here, so for now we do
something much simpler, and remove the cache entirely. This does mean
that monitors now need to perform extra block entity lookups, but the
performance cost doesn't appear to be too bad.
Fixes #1741
2024-03-10 10:57:56 +00:00
|
|
|
private @Nullable MonitorBlockEntity getOrigin() {
|
|
|
|
return getLoadedMonitor(0, 0).getMonitor();
|
2017-05-01 13:32:39 +00:00
|
|
|
}
|
2018-12-23 17:46:58 +00:00
|
|
|
|
2021-11-24 13:35:57 +00:00
|
|
|
/**
|
|
|
|
* Convert monitor coordinates to world coordinates.
|
|
|
|
*
|
|
|
|
* @param x Absolute X position in monitor coordinates
|
|
|
|
* @param y Absolute Y position in monitor coordinates
|
|
|
|
* @return The monitor's position.
|
|
|
|
*/
|
|
|
|
BlockPos toWorldPos(int x, int y) {
|
|
|
|
if (xIndex == x && yIndex == y) return getBlockPos();
|
|
|
|
return getBlockPos().relative(getRight(), -xIndex + x).relative(getDown(), -yIndex + y);
|
2017-05-01 13:32:39 +00:00
|
|
|
}
|
|
|
|
|
2021-11-24 13:35:57 +00:00
|
|
|
void resize(int width, int height) {
|
2018-02-14 21:21:00 +00:00
|
|
|
// If we're not already the origin then we'll need to generate a new terminal.
|
2021-01-15 16:35:49 +00:00
|
|
|
if (xIndex != 0 || yIndex != 0) serverMonitor = null;
|
2018-02-14 21:21:00 +00:00
|
|
|
|
2021-01-15 16:35:49 +00:00
|
|
|
xIndex = 0;
|
|
|
|
yIndex = 0;
|
|
|
|
this.width = width;
|
|
|
|
this.height = height;
|
2018-02-14 21:21:00 +00:00
|
|
|
|
|
|
|
// Determine if we actually need a monitor. In order to do this, simply check if
|
|
|
|
// any component monitor been wrapped as a peripheral. Whilst this flag may be
|
|
|
|
// out of date,
|
|
|
|
var needsTerminal = false;
|
|
|
|
terminalCheck:
|
|
|
|
for (var x = 0; x < width; x++) {
|
|
|
|
for (var y = 0; y < height; y++) {
|
2021-11-24 13:35:57 +00:00
|
|
|
var monitor = getLoadedMonitor(x, y).getMonitor();
|
2020-05-15 16:09:12 +00:00
|
|
|
if (monitor != null && monitor.peripheral != null) {
|
2018-02-14 21:21:00 +00:00
|
|
|
needsTerminal = true;
|
|
|
|
break terminalCheck;
|
2017-05-01 14:48:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-05-01 13:32:39 +00:00
|
|
|
|
2018-02-14 21:21:00 +00:00
|
|
|
// Either delete the current monitor or sync a new one.
|
|
|
|
if (needsTerminal) {
|
2021-01-15 16:35:49 +00:00
|
|
|
if (serverMonitor == null) serverMonitor = new ServerMonitor(advanced, this);
|
2023-10-03 17:20:44 +00:00
|
|
|
|
|
|
|
// Update the terminal's width and height and rebuild it. This ensures the monitor
|
|
|
|
// is consistent when syncing it to other monitors.
|
|
|
|
serverMonitor.rebuild();
|
2018-02-14 21:21:00 +00:00
|
|
|
} else {
|
2023-10-03 17:20:44 +00:00
|
|
|
// Remove the terminal from the serverMonitor, but keep it around - this ensures that we sync
|
|
|
|
// the (now blank) monitor to the client.
|
|
|
|
if (serverMonitor != null) serverMonitor.reset();
|
2018-02-14 21:21:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Update the other monitors, setting coordinates, dimensions and the server terminal
|
2021-11-24 13:35:57 +00:00
|
|
|
var pos = getBlockPos();
|
|
|
|
Direction down = getDown(), right = getRight();
|
2018-02-14 21:21:00 +00:00
|
|
|
for (var x = 0; x < width; x++) {
|
|
|
|
for (var y = 0; y < height; y++) {
|
2021-11-25 13:34:19 +00:00
|
|
|
var other = getLevel().getBlockEntity(pos.relative(right, x).relative(down, y));
|
2022-11-09 23:58:56 +00:00
|
|
|
if (!(other instanceof MonitorBlockEntity monitor) || !isCompatible(monitor)) continue;
|
2018-02-14 21:21:00 +00:00
|
|
|
|
2021-01-15 16:35:49 +00:00
|
|
|
monitor.xIndex = x;
|
|
|
|
monitor.yIndex = y;
|
|
|
|
monitor.width = width;
|
|
|
|
monitor.height = height;
|
|
|
|
monitor.serverMonitor = serverMonitor;
|
2021-11-24 13:35:57 +00:00
|
|
|
monitor.needsUpdate = monitor.needsValidating = false;
|
Update CC: Tweaked to 1.13
Look, I originally had this split into several commits, but lots of
other cleanups got mixed in. I then backported some of the cleanups to
1.12, did other tidy ups there, and eventually the web of merges was
unreadable.
Yes, this is a horrible mess, but it's still nicer than it was. Anyway,
changes:
- Flatten everything. For instance, there are now three instances of
BlockComputer, two BlockTurtle, ItemPocketComputer. There's also no
more BlockPeripheral (thank heavens) - there's separate block classes
for each peripheral type.
- Remove pretty much all legacy code. As we're breaking world
compatibility anyway, we can remove all the code to load worlds from
1.4 days.
- The command system is largely rewriten to take advantage of 1.13's
new system. It's very fancy!
- WidgetTerminal now uses Minecraft's "GUI listener" system.
- BREAKING CHANGE: All the codes in keys.lua are different, due to the
move to LWJGL 3. Hopefully this won't have too much of an impact.
I don't want to map to the old key codes on the Java side, as there
always ends up being small but slight inconsistencies. IMO it's
better to make a clean break - people should be using keys rather
than hard coding the constants anyway.
- commands.list now allows fetching sub-commands. The ROM has already
been updated to allow fancy usage such as commands.time.set("noon").
- Turtles, modems and cables can be waterlogged.
2019-04-02 12:27:27 +00:00
|
|
|
monitor.updateBlockState();
|
2022-11-10 15:48:26 +00:00
|
|
|
BlockEntityHelpers.updateBlock(monitor);
|
2018-02-14 21:21:00 +00:00
|
|
|
}
|
|
|
|
}
|
Ensure the terminal exists when creating a monitor peripheral
Previously we had the invariant that if we had a server monitor, we also
had a terminal. When a monitor shrank into a place, we deleted the
monitor, and then recreated it when a peripheral was requested.
As of ab785a090698e6972caac95691393428c6f8370b this has changed
slightly, and we now just delete the terminal (keeping the ServerMonitor
around). However, we didn't adjust the peripheral code accordingly,
meaning we didn't recreate the /terminal/ when a peripheral was
requested.
The fix for this is very simple - most of the rest of this commit is
some additional code for ensuring monitor invariants hold, so we can
write tests with a little more confidence.
I'm not 100% sold on this approach. It's tricky having a double layer of
nullable state (ServerMonitor, and then the terminal). However, I think
this is reasonable - the ServerMonitor is a reference to the multiblock,
and the Terminal is part of the multiblock's state.
Even after all the refactors, monitor code is still nastier than I'd
like :/.
Fixes #1608
2023-10-09 21:03:43 +00:00
|
|
|
|
|
|
|
assertInvariant();
|
2017-05-01 14:48:44 +00:00
|
|
|
}
|
2017-05-01 13:32:39 +00:00
|
|
|
|
2021-05-04 21:24:58 +00:00
|
|
|
void updateNeighborsDeferred() {
|
|
|
|
needsUpdate = true;
|
|
|
|
}
|
|
|
|
|
Update CC: Tweaked to 1.13
Look, I originally had this split into several commits, but lots of
other cleanups got mixed in. I then backported some of the cleanups to
1.12, did other tidy ups there, and eventually the web of merges was
unreadable.
Yes, this is a horrible mess, but it's still nicer than it was. Anyway,
changes:
- Flatten everything. For instance, there are now three instances of
BlockComputer, two BlockTurtle, ItemPocketComputer. There's also no
more BlockPeripheral (thank heavens) - there's separate block classes
for each peripheral type.
- Remove pretty much all legacy code. As we're breaking world
compatibility anyway, we can remove all the code to load worlds from
1.4 days.
- The command system is largely rewriten to take advantage of 1.13's
new system. It's very fancy!
- WidgetTerminal now uses Minecraft's "GUI listener" system.
- BREAKING CHANGE: All the codes in keys.lua are different, due to the
move to LWJGL 3. Hopefully this won't have too much of an impact.
I don't want to map to the old key codes on the Java side, as there
always ends up being small but slight inconsistencies. IMO it's
better to make a clean break - people should be using keys rather
than hard coding the constants anyway.
- commands.list now allows fetching sub-commands. The ROM has already
been updated to allow fancy usage such as commands.time.set("noon").
- Turtles, modems and cables can be waterlogged.
2019-04-02 12:27:27 +00:00
|
|
|
void expand() {
|
Don't cache the client monitor
When rendering non-origin monitors, we would fetch the origin monitor,
read its client state, and then cache that on the current monitor to
avoid repeated lookups.
However, if the origin monitor is unloaded/removed on the client, and
then loaded agin, this cache will be not be invalidated, causing us to
render both the old and new monitor!
I think the correct thing to do here is cache the origin monitor. This
allows us to check when the origin monitor has been removed, and
invalidate the cache if needed.
However, I'm wary of any other edge cases here, so for now we do
something much simpler, and remove the cache entirely. This does mean
that monitors now need to perform extra block entity lookups, but the
performance cost doesn't appear to be too bad.
Fixes #1741
2024-03-10 10:57:56 +00:00
|
|
|
var monitor = getOrigin();
|
2021-11-24 13:35:57 +00:00
|
|
|
if (monitor != null && monitor.xIndex == 0 && monitor.yIndex == 0) new Expander(monitor).expand();
|
2017-05-01 14:48:44 +00:00
|
|
|
}
|
2018-12-23 17:46:58 +00:00
|
|
|
|
2021-11-24 13:35:57 +00:00
|
|
|
private void contractNeighbours() {
|
|
|
|
if (width == 1 && height == 1) return;
|
|
|
|
|
|
|
|
var pos = getBlockPos();
|
|
|
|
Direction down = getDown(), right = getRight();
|
|
|
|
var origin = toWorldPos(0, 0);
|
|
|
|
|
2022-11-09 23:58:56 +00:00
|
|
|
MonitorBlockEntity toLeft = null, toAbove = null, toRight = null, toBelow = null;
|
2021-11-24 13:35:57 +00:00
|
|
|
if (xIndex > 0) toLeft = tryResizeAt(pos.relative(right, -xIndex), xIndex, 1);
|
|
|
|
if (yIndex > 0) toAbove = tryResizeAt(origin, width, yIndex);
|
|
|
|
if (xIndex < width - 1) toRight = tryResizeAt(pos.relative(right, 1), width - xIndex - 1, 1);
|
|
|
|
if (yIndex < height - 1) {
|
|
|
|
toBelow = tryResizeAt(origin.relative(down, yIndex + 1), width, height - yIndex - 1);
|
2017-05-01 14:48:44 +00:00
|
|
|
}
|
2021-11-24 13:35:57 +00:00
|
|
|
|
|
|
|
if (toLeft != null) toLeft.expand();
|
|
|
|
if (toAbove != null) toAbove.expand();
|
|
|
|
if (toRight != null) toRight.expand();
|
|
|
|
if (toBelow != null) toBelow.expand();
|
2017-05-01 14:48:44 +00:00
|
|
|
}
|
2018-12-23 17:46:58 +00:00
|
|
|
|
2021-11-24 13:35:57 +00:00
|
|
|
@Nullable
|
2022-11-09 23:58:56 +00:00
|
|
|
private MonitorBlockEntity tryResizeAt(BlockPos pos, int width, int height) {
|
2024-04-06 07:38:44 +00:00
|
|
|
var tile = getLevel().getBlockEntity(pos);
|
2022-11-09 23:58:56 +00:00
|
|
|
if (tile instanceof MonitorBlockEntity monitor && isCompatible(monitor)) {
|
2021-11-24 13:35:57 +00:00
|
|
|
monitor.resize(width, height);
|
|
|
|
return monitor;
|
2017-05-01 14:48:44 +00:00
|
|
|
}
|
2018-12-23 17:46:58 +00:00
|
|
|
|
2021-11-24 13:35:57 +00:00
|
|
|
return null;
|
2017-05-01 14:48:44 +00:00
|
|
|
}
|
2018-12-23 17:46:58 +00:00
|
|
|
|
2021-11-24 13:35:57 +00:00
|
|
|
|
2021-07-25 13:18:07 +00:00
|
|
|
private boolean checkMonitorAt(int xIndex, int yIndex) {
|
2021-11-24 13:35:57 +00:00
|
|
|
var state = getLoadedMonitor(xIndex, yIndex);
|
2021-07-25 13:18:07 +00:00
|
|
|
if (state.isMissing()) return false;
|
|
|
|
|
|
|
|
var monitor = state.getMonitor();
|
|
|
|
if (monitor == null) return true;
|
|
|
|
|
|
|
|
return monitor.xIndex == xIndex && monitor.yIndex == yIndex && monitor.width == width && monitor.height == height;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void validate() {
|
2021-11-24 13:35:57 +00:00
|
|
|
if (xIndex == 0 && yIndex == 0 && width == 1 && height == 1) return;
|
2021-07-25 13:18:07 +00:00
|
|
|
|
2022-11-09 20:10:24 +00:00
|
|
|
if (xIndex >= 0 && xIndex <= width && width > 0 && width <= Config.monitorWidth &&
|
|
|
|
yIndex >= 0 && yIndex <= height && height > 0 && height <= Config.monitorHeight &&
|
2021-11-24 13:35:57 +00:00
|
|
|
checkMonitorAt(0, 0) && checkMonitorAt(0, height - 1) &&
|
2021-07-25 13:18:07 +00:00
|
|
|
checkMonitorAt(width - 1, 0) && checkMonitorAt(width - 1, height - 1)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Something in our monitor is invalid. For now, let's just reset ourselves and then try to integrate ourselves
|
|
|
|
// later.
|
2022-11-09 20:10:24 +00:00
|
|
|
LOG.warn("Monitor is malformed, resetting to 1x1.");
|
2021-07-25 13:18:07 +00:00
|
|
|
resize(1, 1);
|
|
|
|
needsUpdate = true;
|
|
|
|
}
|
2021-11-24 13:35:57 +00:00
|
|
|
// endregion
|
2021-07-25 13:18:07 +00:00
|
|
|
|
2022-11-10 15:48:26 +00:00
|
|
|
void monitorTouched(float xPos, float yPos, float zPos) {
|
2022-10-21 17:26:57 +00:00
|
|
|
if (!advanced) return;
|
|
|
|
|
Update CC: Tweaked to 1.13
Look, I originally had this split into several commits, but lots of
other cleanups got mixed in. I then backported some of the cleanups to
1.12, did other tidy ups there, and eventually the web of merges was
unreadable.
Yes, this is a horrible mess, but it's still nicer than it was. Anyway,
changes:
- Flatten everything. For instance, there are now three instances of
BlockComputer, two BlockTurtle, ItemPocketComputer. There's also no
more BlockPeripheral (thank heavens) - there's separate block classes
for each peripheral type.
- Remove pretty much all legacy code. As we're breaking world
compatibility anyway, we can remove all the code to load worlds from
1.4 days.
- The command system is largely rewriten to take advantage of 1.13's
new system. It's very fancy!
- WidgetTerminal now uses Minecraft's "GUI listener" system.
- BREAKING CHANGE: All the codes in keys.lua are different, due to the
move to LWJGL 3. Hopefully this won't have too much of an impact.
I don't want to map to the old key codes on the Java side, as there
always ends up being small but slight inconsistencies. IMO it's
better to make a clean break - people should be using keys rather
than hard coding the constants anyway.
- commands.list now allows fetching sub-commands. The ROM has already
been updated to allow fancy usage such as commands.time.set("noon").
- Turtles, modems and cables can be waterlogged.
2019-04-02 12:27:27 +00:00
|
|
|
var pair = XYPair
|
|
|
|
.of(xPos, yPos, zPos, getDirection(), getOrientation())
|
2021-01-15 16:35:49 +00:00
|
|
|
.add(xIndex, height - yIndex - 1);
|
2017-05-01 13:32:39 +00:00
|
|
|
|
2022-10-25 21:58:31 +00:00
|
|
|
if (pair.x() > width - RENDER_BORDER || pair.y() > height - RENDER_BORDER || pair.x() < RENDER_BORDER || pair.y() < RENDER_BORDER) {
|
2017-05-01 14:48:44 +00:00
|
|
|
return;
|
|
|
|
}
|
2018-02-14 21:21:00 +00:00
|
|
|
|
2022-10-21 18:00:29 +00:00
|
|
|
var serverTerminal = getServerMonitor();
|
2022-10-21 17:26:57 +00:00
|
|
|
if (serverTerminal == null) return;
|
2018-02-14 21:21:00 +00:00
|
|
|
|
|
|
|
Terminal originTerminal = serverTerminal.getTerminal();
|
|
|
|
if (originTerminal == null) return;
|
|
|
|
|
2021-01-15 16:35:49 +00:00
|
|
|
var xCharWidth = (width - (RENDER_BORDER + RENDER_MARGIN) * 2.0) / originTerminal.getWidth();
|
|
|
|
var yCharHeight = (height - (RENDER_BORDER + RENDER_MARGIN) * 2.0) / originTerminal.getHeight();
|
2018-02-14 21:21:00 +00:00
|
|
|
|
2022-10-25 21:58:31 +00:00
|
|
|
var xCharPos = (int) Math.min(originTerminal.getWidth(), Math.max((pair.x() - RENDER_BORDER - RENDER_MARGIN) / xCharWidth + 1.0, 1.0));
|
|
|
|
var yCharPos = (int) Math.min(originTerminal.getHeight(), Math.max((pair.y() - RENDER_BORDER - RENDER_MARGIN) / yCharHeight + 1.0, 1.0));
|
2018-02-14 21:21:00 +00:00
|
|
|
|
2021-11-24 13:35:57 +00:00
|
|
|
eachComputer(c -> c.queueEvent("monitor_touch", c.getAttachmentName(), xCharPos, yCharPos));
|
|
|
|
}
|
|
|
|
|
|
|
|
private void eachComputer(Consumer<IComputerAccess> fun) {
|
|
|
|
for (var x = 0; x < width; x++) {
|
|
|
|
for (var y = 0; y < height; y++) {
|
|
|
|
var monitor = getLoadedMonitor(x, y).getMonitor();
|
2018-12-23 17:46:58 +00:00
|
|
|
if (monitor == null) continue;
|
2018-02-14 21:21:00 +00:00
|
|
|
|
2021-11-24 13:35:57 +00:00
|
|
|
for (var computer : monitor.computers) fun.accept(computer);
|
2017-05-01 14:48:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-02-14 21:21:00 +00:00
|
|
|
|
2022-11-06 22:26:07 +00:00
|
|
|
public IPeripheral peripheral() {
|
Ensure the terminal exists when creating a monitor peripheral
Previously we had the invariant that if we had a server monitor, we also
had a terminal. When a monitor shrank into a place, we deleted the
monitor, and then recreated it when a peripheral was requested.
As of ab785a090698e6972caac95691393428c6f8370b this has changed
slightly, and we now just delete the terminal (keeping the ServerMonitor
around). However, we didn't adjust the peripheral code accordingly,
meaning we didn't recreate the /terminal/ when a peripheral was
requested.
The fix for this is very simple - most of the rest of this commit is
some additional code for ensuring monitor invariants hold, so we can
write tests with a little more confidence.
I'm not 100% sold on this approach. It's tricky having a double layer of
nullable state (ServerMonitor, and then the terminal). However, I think
this is reasonable - the ServerMonitor is a reference to the multiblock,
and the Terminal is part of the multiblock's state.
Even after all the refactors, monitor code is still nastier than I'd
like :/.
Fixes #1608
2023-10-09 21:03:43 +00:00
|
|
|
createServerTerminal();
|
|
|
|
var peripheral = this.peripheral != null ? this.peripheral : (this.peripheral = new MonitorPeripheral(this));
|
|
|
|
assertInvariant();
|
|
|
|
return peripheral;
|
2022-11-06 22:26:07 +00:00
|
|
|
}
|
|
|
|
|
2019-01-25 22:59:01 +00:00
|
|
|
void addComputer(IComputerAccess computer) {
|
2021-01-15 16:35:49 +00:00
|
|
|
computers.add(computer);
|
2017-05-01 14:48:44 +00:00
|
|
|
}
|
2018-12-23 17:46:58 +00:00
|
|
|
|
2019-01-25 22:59:01 +00:00
|
|
|
void removeComputer(IComputerAccess computer) {
|
2021-01-15 16:35:49 +00:00
|
|
|
computers.remove(computer);
|
2017-05-01 14:48:44 +00:00
|
|
|
}
|
2018-12-23 17:46:58 +00:00
|
|
|
|
2022-11-09 23:58:56 +00:00
|
|
|
@ForgeOverride
|
2021-08-03 20:46:53 +00:00
|
|
|
public AABB getRenderBoundingBox() {
|
Cleanup and optimise terminal rendering (#1057)
- Remove the POSITION_COLOR render type. Instead we just render a
background terminal quad as the pocket computer light - it's a little
(lot?) more cheaty, but saves having to create a render type.
- Use the existing position_color_tex shader instead of our copy. I
looked at using RenderType.text, but had a bunch of problems with GUI
terminals. Its possible we can fix it, but didn't want to spend too
much time on it.
- Remove some methods from FixedWidthFontRenderer, inlining them into
the call site.
- Switch back to using GL_QUADS rather than GL_TRIANGLES. I know Lig
will shout at me for this, but the rest of MC uses QUADS, so I don't
think best practice really matters here.
- Fix the TBO backend monitor not rendering monitors with fog.
Unfortunately we can't easily do this to the VBO one without writing
a custom shader (which defeats the whole point of the VBO backend!),
as the distance calculation of most render types expect an
already-transformed position (camera-relative I think!) while we pass
a world-relative one.
- When rendering to a VBO we push vertices to a ByteBuffer directly,
rather than going through MC's VertexConsumer system. This removes
the overhead which comes with VertexConsumer, significantly improving
performance.
- Pre-convert palette colours to bytes, storing both the coloured and
greyscale versions as a byte array. This allows us to remove the
multiple casts and conversions (double -> float -> (greyscale) ->
byte), offering noticeable performance improvements (multiple ms per
frame).
We're using a byte[] here rather than a record of three bytes as
notionally it provides better performance when writing to a
ByteBuffer directly compared to calling .put() four times. [^1]
- Memorize getRenderBoundingBox. This was taking about 5% of the total
time on the render thread[^2], so worth doing.
I don't actually think the allocation is the heavy thing here -
VisualVM says it's toWorldPos being slow. I'm not sure why - possibly
just all the block property lookups? [^2]
Note that none of these changes improve compatibility with Optifine.
Right now there's some serious issues where monitors are writing _over_
blocks in front of them. To fix this, we probably need to remove the
depth blocker and just render characters with a z offset. Will do that
in a separate commit, as I need to evaluate how well that change will
work first.
The main advantage of this commit is the improved performance. In my
stress test with 120 monitors updating every tick, I'm getting 10-20fps
[^3] (still much worse than TBOs, which manages a solid 60-100).
In practice, we'll actually be much better than this. Our network
bandwidth limits means only 40 change in a single tick - and so FPS is
much more reasonable (+60fps).
[^1]: In general, put(byte[]) is faster than put(byte) multiple times.
Just not clear if this is true when dealing with a small (and loop
unrolled) number of bytes.
[^2]: To be clear, this is with 120 monitors and no other block entities
with custom renderers. so not really representative.
[^3]: I wish I could provide a narrower range, but it varies so much
between me restarting the game. Makes it impossible to benchmark
anything!
2022-04-02 09:54:03 +00:00
|
|
|
// We attempt to cache the bounding box to save having to do property lookups (and allocations!) on every frame.
|
|
|
|
// Unfortunately the AABB does depend on quite a lot of state, so we need to add a bunch of extra fields -
|
|
|
|
// ideally these'd be a single object, but I don't think worth doing until Java has value types.
|
|
|
|
if (boundingBox != null && getBlockState().equals(bbState) && getBlockPos().equals(bbPos) &&
|
|
|
|
xIndex == bbX && yIndex == bbY && width == bbWidth && height == bbHeight) {
|
|
|
|
return boundingBox;
|
|
|
|
}
|
|
|
|
|
|
|
|
bbState = getBlockState();
|
|
|
|
bbPos = getBlockPos();
|
|
|
|
bbX = xIndex;
|
|
|
|
bbY = yIndex;
|
|
|
|
bbWidth = width;
|
|
|
|
bbHeight = height;
|
|
|
|
|
2021-11-24 13:35:57 +00:00
|
|
|
var startPos = toWorldPos(0, 0);
|
|
|
|
var endPos = toWorldPos(width, height);
|
Cleanup and optimise terminal rendering (#1057)
- Remove the POSITION_COLOR render type. Instead we just render a
background terminal quad as the pocket computer light - it's a little
(lot?) more cheaty, but saves having to create a render type.
- Use the existing position_color_tex shader instead of our copy. I
looked at using RenderType.text, but had a bunch of problems with GUI
terminals. Its possible we can fix it, but didn't want to spend too
much time on it.
- Remove some methods from FixedWidthFontRenderer, inlining them into
the call site.
- Switch back to using GL_QUADS rather than GL_TRIANGLES. I know Lig
will shout at me for this, but the rest of MC uses QUADS, so I don't
think best practice really matters here.
- Fix the TBO backend monitor not rendering monitors with fog.
Unfortunately we can't easily do this to the VBO one without writing
a custom shader (which defeats the whole point of the VBO backend!),
as the distance calculation of most render types expect an
already-transformed position (camera-relative I think!) while we pass
a world-relative one.
- When rendering to a VBO we push vertices to a ByteBuffer directly,
rather than going through MC's VertexConsumer system. This removes
the overhead which comes with VertexConsumer, significantly improving
performance.
- Pre-convert palette colours to bytes, storing both the coloured and
greyscale versions as a byte array. This allows us to remove the
multiple casts and conversions (double -> float -> (greyscale) ->
byte), offering noticeable performance improvements (multiple ms per
frame).
We're using a byte[] here rather than a record of three bytes as
notionally it provides better performance when writing to a
ByteBuffer directly compared to calling .put() four times. [^1]
- Memorize getRenderBoundingBox. This was taking about 5% of the total
time on the render thread[^2], so worth doing.
I don't actually think the allocation is the heavy thing here -
VisualVM says it's toWorldPos being slow. I'm not sure why - possibly
just all the block property lookups? [^2]
Note that none of these changes improve compatibility with Optifine.
Right now there's some serious issues where monitors are writing _over_
blocks in front of them. To fix this, we probably need to remove the
depth blocker and just render characters with a z offset. Will do that
in a separate commit, as I need to evaluate how well that change will
work first.
The main advantage of this commit is the improved performance. In my
stress test with 120 monitors updating every tick, I'm getting 10-20fps
[^3] (still much worse than TBOs, which manages a solid 60-100).
In practice, we'll actually be much better than this. Our network
bandwidth limits means only 40 change in a single tick - and so FPS is
much more reasonable (+60fps).
[^1]: In general, put(byte[]) is faster than put(byte) multiple times.
Just not clear if this is true when dealing with a small (and loop
unrolled) number of bytes.
[^2]: To be clear, this is with 120 monitors and no other block entities
with custom renderers. so not really representative.
[^3]: I wish I could provide a narrower range, but it varies so much
between me restarting the game. Makes it impossible to benchmark
anything!
2022-04-02 09:54:03 +00:00
|
|
|
return boundingBox = new AABB(
|
2021-11-24 13:35:57 +00:00
|
|
|
Math.min(startPos.getX(), endPos.getX()),
|
|
|
|
Math.min(startPos.getY(), endPos.getY()),
|
|
|
|
Math.min(startPos.getZ(), endPos.getZ()),
|
|
|
|
Math.max(startPos.getX(), endPos.getX()) + 1,
|
|
|
|
Math.max(startPos.getY(), endPos.getY()) + 1,
|
|
|
|
Math.max(startPos.getZ(), endPos.getZ()) + 1
|
|
|
|
);
|
2017-05-01 13:32:39 +00:00
|
|
|
}
|
Ensure the terminal exists when creating a monitor peripheral
Previously we had the invariant that if we had a server monitor, we also
had a terminal. When a monitor shrank into a place, we deleted the
monitor, and then recreated it when a peripheral was requested.
As of ab785a090698e6972caac95691393428c6f8370b this has changed
slightly, and we now just delete the terminal (keeping the ServerMonitor
around). However, we didn't adjust the peripheral code accordingly,
meaning we didn't recreate the /terminal/ when a peripheral was
requested.
The fix for this is very simple - most of the rest of this commit is
some additional code for ensuring monitor invariants hold, so we can
write tests with a little more confidence.
I'm not 100% sold on this approach. It's tricky having a double layer of
nullable state (ServerMonitor, and then the terminal). However, I think
this is reasonable - the ServerMonitor is a reference to the multiblock,
and the Terminal is part of the multiblock's state.
Even after all the refactors, monitor code is still nastier than I'd
like :/.
Fixes #1608
2023-10-09 21:03:43 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Assert all {@linkplain #checkInvariants() monitor invariants} hold.
|
|
|
|
*/
|
|
|
|
private void assertInvariant() {
|
|
|
|
assert checkInvariants() : "Monitor invariants failed. See logs.";
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check various invariants about this monitor multiblock. This is only called when assertions are enabled, so
|
|
|
|
* will be skipped outside of tests.
|
|
|
|
*
|
|
|
|
* @return Whether all invariants passed.
|
|
|
|
*/
|
|
|
|
private boolean checkInvariants() {
|
|
|
|
LOG.debug("Checking monitor invariants at {}", getBlockPos());
|
|
|
|
|
|
|
|
var okay = true;
|
|
|
|
|
|
|
|
if (width <= 0 || height <= 0) {
|
|
|
|
okay = false;
|
|
|
|
LOG.error("Monitor {} has non-positive of {}x{}", getBlockPos(), width, height);
|
|
|
|
}
|
|
|
|
|
|
|
|
var hasPeripheral = false;
|
Don't cache the client monitor
When rendering non-origin monitors, we would fetch the origin monitor,
read its client state, and then cache that on the current monitor to
avoid repeated lookups.
However, if the origin monitor is unloaded/removed on the client, and
then loaded agin, this cache will be not be invalidated, causing us to
render both the old and new monitor!
I think the correct thing to do here is cache the origin monitor. This
allows us to check when the origin monitor has been removed, and
invalidate the cache if needed.
However, I'm wary of any other edge cases here, so for now we do
something much simpler, and remove the cache entirely. This does mean
that monitors now need to perform extra block entity lookups, but the
performance cost doesn't appear to be too bad.
Fixes #1741
2024-03-10 10:57:56 +00:00
|
|
|
var origin = getOrigin();
|
Ensure the terminal exists when creating a monitor peripheral
Previously we had the invariant that if we had a server monitor, we also
had a terminal. When a monitor shrank into a place, we deleted the
monitor, and then recreated it when a peripheral was requested.
As of ab785a090698e6972caac95691393428c6f8370b this has changed
slightly, and we now just delete the terminal (keeping the ServerMonitor
around). However, we didn't adjust the peripheral code accordingly,
meaning we didn't recreate the /terminal/ when a peripheral was
requested.
The fix for this is very simple - most of the rest of this commit is
some additional code for ensuring monitor invariants hold, so we can
write tests with a little more confidence.
I'm not 100% sold on this approach. It's tricky having a double layer of
nullable state (ServerMonitor, and then the terminal). However, I think
this is reasonable - the ServerMonitor is a reference to the multiblock,
and the Terminal is part of the multiblock's state.
Even after all the refactors, monitor code is still nastier than I'd
like :/.
Fixes #1608
2023-10-09 21:03:43 +00:00
|
|
|
var serverMonitor = origin != null ? origin.serverMonitor : this.serverMonitor;
|
|
|
|
for (var x = 0; x < width; x++) {
|
|
|
|
for (var y = 0; y < height; y++) {
|
|
|
|
var monitor = getLoadedMonitor(x, y).getMonitor();
|
|
|
|
if (monitor == null) continue;
|
|
|
|
|
|
|
|
hasPeripheral |= monitor.peripheral != null;
|
|
|
|
|
|
|
|
if (monitor.serverMonitor != null && monitor.serverMonitor != serverMonitor) {
|
|
|
|
okay = false;
|
|
|
|
LOG.error(
|
|
|
|
"Monitor {} expected to be have serverMonitor={}, but was {}",
|
|
|
|
monitor.getBlockPos(), serverMonitor, monitor.serverMonitor
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (monitor.xIndex != x || monitor.yIndex != y) {
|
|
|
|
okay = false;
|
|
|
|
LOG.error(
|
|
|
|
"Monitor {} expected to be at {},{}, but believes it is {},{}",
|
|
|
|
monitor.getBlockPos(), x, y, monitor.xIndex, monitor.yIndex
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (monitor.width != width || monitor.height != height) {
|
|
|
|
okay = false;
|
|
|
|
LOG.error(
|
|
|
|
"Monitor {} expected to be size {},{}, but believes it is {},{}",
|
|
|
|
monitor.getBlockPos(), width, height, monitor.width, monitor.height
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
var expectedState = getBlockState().setValue(MonitorBlock.STATE, MonitorEdgeState.fromConnections(
|
|
|
|
y < height - 1, y > 0, x > 0, x < width - 1
|
|
|
|
));
|
|
|
|
if (monitor.getBlockState() != expectedState) {
|
|
|
|
okay = false;
|
|
|
|
LOG.error(
|
|
|
|
"Monitor {} expected to have state {}, but has state {}",
|
|
|
|
monitor.getBlockState(), expectedState, monitor.getBlockState()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hasPeripheral != (serverMonitor != null && serverMonitor.getTerminal() != null)) {
|
|
|
|
okay = false;
|
|
|
|
LOG.error(
|
|
|
|
"Peripheral is {}, but serverMonitor={} and serverMonitor.terminal={}",
|
|
|
|
hasPeripheral, serverMonitor, serverMonitor == null ? null : serverMonitor.getTerminal()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return okay;
|
|
|
|
}
|
2017-05-01 13:32:39 +00:00
|
|
|
}
|