When adding/removing observers, we locked on the observer, then
acquired the global lock. When a metric is observed, then we acquire the
global lock and then the observer lock.
If these happen at the same time, we can easily end up with a deadlock.
We simply avoid holding the observer lock for the entire add/remove
process (instead only locking when actually needed).
Closes#1639
This commit adds abstract classes to describe the interface for our
mod-loader-specific generic peripherals (inventories, fluid storage,
item storage).
This offers several advantages:
- Javadoc to illuaminate conversion no longer needs the Forge project
(just core and common).
- Ensures we have a consistent interface between Forge and Fabric.
Note, this does /not/ implement fluid or energy storage for Fabric. We
probably could do fluid without issue, but not something worth doing
right now.
This adds a new "java_allocation" metric, which tracks the number of
bytes allocated while executing the computer (as measured by Java). This
is not an 100% reliable number, but hopefully gives some insight into
what computers are doing.
Historically CC has supported two modes when working with file handles
(and HTTP requests):
- Text mode, which reads/write using UTF-8.
- Binary mode, which reads/writes the raw bytes.
However, this can be confusing at times. CC/Lua doesn't actually support
unicode, so any characters beyond the 0.255 range were replaced with
'?'. This meant that most of the time you were better off just using
binary mode.
This commit unifies text and binary mode - we now /always/ read the raw
bytes of the file, rather than converting to/from UTF-8. Binary mode now
only specifies whether handle.read() returns a number (and .write(123)
writes a byte rather than coercing to a string).
- Refactor the entire handle hierarchy. We now have an AbstractMount
base class, which has the concrete implementation of all methods. The
public-facing classes then re-export these methods by annotating
them with @LuaFunction.
These implementations are based on the
Binary{Readable,Writable}Handle classes. The Encoded{..}Handle
versions are now entirely removed.
- As we no longer need to use BufferedReader/BufferedWriter, we can
remove quite a lot of logic in Filesystem to handle wrapping
closeable objects.
- Add a new WritableMount.openFile method, which generalises
openForWrite/openForAppend to accept OpenOptions. This allows us to
support update mode (r+, w+) in fs.open.
- fs.open now uses the new handle types, and supports update (r+, w+)
mode.
- http.request now uses the new readable handle type. We no longer
encode the request body to UTF-8, nor decode the response from UTF-8.
- Websockets now return text frame's contents directly, rather than
converting it from UTF-8. Sending text frames now attempts to treat
the passed string as UTF-8, rather than treating it as latin1.
Does it count as an emulator when it's official? I hope not, as this'd
make it my fourth or fifth emulator at this point.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Developing/debugging CraftOS is a massive pain to do inside Minecraft,
as any change to resources requires a compile+hot swap cycle (and
sometimes a `/reload` in-game). As such, it's often more convenient to
spin up an emulator, pointing it to load the ROM from CC:T's sources.
However, this isn't practical when also making changes to the Java
classes. In this case, we either need to go in-game, or build a custom
version of CCEmuX.
This commit offers an alternative option: we now have our own emulator,
which allows us to hot swap both Lua and Java to our heart's content.
Most of the code here is based on our monitor TBO renderer. We probably
could share some more of this, but there's not really a good place for
it - feels a bit weird just to chuck it in :core.
This is *not* a general-purpose emulator. It's limited in a lot of
ways (won't launch on Mac[^1], no support for multiple computers) - just
stick to what's there already.
[^1]: We require OpenGL 4.5 due to our use of DSA.
In practice, we're never going to change this to true by default. The
old Tekkit Legends pack enabled this[^1], and that caused a lot of
problems, though admittedly back in 2016 so things might be better now.
If people do want this functionality, it should be fairly easy to
replicate with a datapack, adding a file to rom/autorun.
[^1]: See https://www.computercraft.info/forums2/index.php?/topic/27663-
Hate that I remember this, why is this still in my brain?
- Move most error message constants to a new MountHelpers class.
- Be a little more consistent in when we throw "No such file" vs "Not a
file/directory" messages.
The two implementations aren't entirely compatible - the implementation
returned by .of will throw an NPE on .contains(null), whereas the
Collections implementations just return false. However, we try to avoid
passing null to collections methods, so this should be safe.
There's no strong reason to do this, but it helps make the code a little
more consistent
As of 1.20, sign messages are immutable - we need to do
text = text.setMesssage(...) instead. Also do a tiny bit of cleanup to
this function while we're here.
Probably not the best use of my lunch break :D:.
Fixes#1611.
Allows registering arbitrary block lookup functions instead of a
platform-specific capability. This is roughly what Fabric did before,
but generalised to also take an invalidation callback.
This callback is a little nasty - it needs to be a NonNullableConsumer
on Forge, but that class isn't available on Fabric. For now, we make the
lookup function (and thus the generic peripheral provider) generic on
some <T extends Runnable> type, then specialise that on the Forge side.
Hopefully we can clean this up when NeoForge reworks capabilities.
Or rather, being published to the wrong place. The java-convention
plugin sets the group, but that was applied after the publishing one - I
was hoping it'd read that property lazy, but clearly not!
Wow, some of this is /old/. All the Maps.newHashMap stuff dates back to
Java 6, so must originally be CCTweaks code?!
We're unlikely to drop our Guava dependency (we use too much other
stuff), but we should make the most of the stdlib where possible.
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 ab785a0906 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
We can't use FriendlyByte.readCollection to read to a
pre-allocated/array-backed NonNullList, as that doesn't implement
List.add. Instead, we just need to do a normal loop.
We add a couple of tests to round-trip our recipe specs. Unfortunately
we can't test the recipes themselves as our own registries aren't set
up, so this'll have to do for now.
Oh, this was a really nasty bug to reproduce. I'm not sure why - it's
very simple - I guess I've only just seen screenshots of it, and never
sat down to try myself. Reminder to actually report your bugs folks!
In this case:
1. Place down three down three monitors and then a computer.
2. Display something on the monitor (monitor left paint a) is my go-to.
3. Break the middle monitor.
We'd expect the left most monitor to be cleared, however it actually
preserves the monitor contents, resizing (and skewing it) to fit on its
new size!
This is because we clear the server monitor, but never sync that over to
the client, so the client monitor retains the old contents. To fix that,
instead of nulling out the server monitor, we null out the underlying
Terminal. This causes the change to be synced, fixing the bug.
This moves MemoryMount to the main core module, and converts it to be a
"proper" WritableMount. It's still naively implemented - definitely
would be good to flesh out our tests in the future - but enough for what
we need it for.
We also do the following:
- Remove the FileEntry.path variable, and instead pass the path around
as a variable.
- Clean up BinaryReadableHandle to use ByteBuffers in a more idiomatic
way.
- Add a couple more tests to our FS tests. These are in a bit of an odd
place, where we want both Lua tests (for emulator compliance) and
Java tests (for testing different implementations) - something to
think about in the future.
This attempts to reduce some duplication in recipe serialisation (and
deserialisation) by moving the structure of a recipe (group, category,
ingredients, result) into seprate types.
- Add ShapedRecipeSpec and ShapelessRecipeSpec, which store the core
properties of shaped and shapeless recipes. There's a couple of
additional classes here for handling some of the other shared or
complex logic.
- These classes are now used by two new Custom{Shaped,Shapeless}Recipe
classes, which are (mostly) equivalent to Minecraft's
shaped/shapeless recipes, just with support for nbt in results.
- All the other similar recipes now inherit from these base classes,
which allows us to reuse a lot of this serialisation code. Alas, the
total code size has still gone up - maybe there's too much
abstraction here :).
- Mostly unrelated, but fix the skull recipes using the wrong UUID
format.
This allows us to remove our mixin for nbt in recipes (as we just use
our custom recipe now) and simplify serialisation a bit - hopefully
making the switch to codecs a little easier.
- Add AbstractInMemoryMount, which contains all of ArchiveMount's file
tree logic, but not the caching functionality.
- Convert MemoryMount to inherit from AbstractInMemoryMount.
- Add a helper method to add a file to an AbstractInMemoryMount, and
use that within {Resource,Jar}Mount.
There's definitely more work to be done here - it might be nice to split
FileEntry into separate Directory and File interfaces, or at least make
them slightly more immutable, but that's definitely a future job.
- Placing a command computer requires the player to be in creative and
opped.
- Breaking a command computer now requires the player to be opped, as
well as in creative.
As we've now got a dedicated item class for command comptuers, we move
the command-specific IMedia override to that class.
Fixes#1582.
Rather than having a mess of lambdas, we now move the bulk of the
implemetation to their own methods. The lambdas now just do argument
extraction - it's all stringly typed, so good to keep that with the
argument definition.
This also removes a couple of exception keys (and thus their translation
keys) as we no longer use them.
Wow, this is old. It looks like it's a legacy of when this method was on
TileGeneric (and so returned false by default). As all implementations
now return true (turtle tools no longer block redstone), we don't really
need this any more.
- Split buttons.png into individual textures.
- Split corners_xyz.png into the following:
- borders_xyz.png: A nine-sliced texture of the computer borders.
- pocket_bottom_xyz.png: A horizontally 3-sliced texture of the
bottom part of a pocket computer.
- sidebar_xyz.png: A vertically 3-sliced texture of the computer
sidebar.
While not splitting the sliced textures into smaller ones may seem a
little odd, it's consistent with what vanilla does in 1.20.2, and I
think will make editing them easier than juggling 9 textures.
I do want to make this more data-driven in the future, but that will
have to wait until the changes in 1.20.2.
This also adds a tools/update-resources.py program, which performs this
transformation on a given resource pack.
- Add a generic PermissionRegistry interface. This behaves similarly to
our ShaderMod interface, searching all providers until it finds a
compatible one.
We could just make this part of the platform code instead, but this
allows us to support multiple systems on Fabric, where things are
less standardised.
This interface behaves like a registry, rather than a straight
`getPermission(node, player)` method, as Forge requires us to list
our nodes up-front.
- Add Forge (using the built-in system) and Fabric (using
fabric-permissions-api) implementations of the above interface.
- Register permission nodes for our commands, and use those
instead. This does mean that the permissions check for the root
/computercraft command now requires enumerating all child
commands (and so potential does 7 permission lookups), but hopefully
this isn't too bad in practice.
- Remove UserLevel.OWNER - we never used this anywhere, and I can't
imagine we'll want to in the future.
Should be max_websocket_message, not just websocket_message.
Also add some additional validation to address rules, to check no
unrecognised keys are present.
Closes#1566.
We're very inconsistent with whether we use locks or concurrent maps
here. Something to sort out in the future, but for now add some missing
@GuardedBy annotations.
- Prefer {read,write}Nullable when possible.
- Use SoundEvent.{writeTo,readFrom}Network, instead of sending the
registry entries. This allows playing discs which don't register
their SoundEvent on the server.
- Add a couple of tests for round-tripping these packets.
This requires supporting registries in our platform test
code. Thankfully this is mostly the same as what we can do in Fabric -
the duplication is unfortunate - but it's easy enough.
This made more sense on 1.19.2 and before, but now that we have to do
this for tooltips, we might as well do it for messages as well.
Closes#1538, though hopefully will be resolved on the VO side too.
- Remove some unused translation keys.
- Run tools/language.py to sort the current translations and remove the
aforementioned unused keys.
- Update turtle tool impostor recipes - these now include the tool NBT!
Translations for Polish
Translations for French
Translations for Spanish
Translations for German
Co-authored-by: Patriik <apatriik0@gmail.com>
Co-authored-by: Sammy <SammyKoch@pm.me>
Co-authored-by: SquidDev <git@squiddev.cc>
- Move the tool action before the "is block present" check, fixes
#1527. This is where it was before, but we flipped it around in the
tool rewrite.
- Don't reuse as much turtle.place logic for tool actions. This fixes
some instances where tools could till/level dirt through solid
blocks.