- Debug hooks are now correctly called for every function.
- Fix several minor inconsistencies with debug.getinfo.
- Fix Lua tables being sized incorrectly when created from varargs.
This was copied over from the old binary handle, and so states we
always return a single number if no count is given. This is only the
case when the file is opened in binary mode.
I was able to reproduce this by starting two computers, and then warming
up the JIT by running:
while true do os.queueEvent("x") os.pullEvent("x") end
and then running the following on one computer, while typing on the
other:
while true do end
I'm not quite sure why this happens. It's possible that once the JIT is
warm, we can resume computers without actually allocating anything,
though I'm a little unconvinced.
Fixes#1672
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.
Rather than assuming static methods are generic, and instance methods
are direct, the Generator now has separate entrypoints for handling
instance and generic methods.
As a result of this change, we've also relaxed some of the validation
code. As a result, we now allow calling private/protected methods
which are annotated with @LuaFunction.
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.
- Update Cobalt to 0.8.0, switching our Lua version to 5.2(ish).
- Remove our `load` wrapper, as we no longer need to inject _ENV into
the enviroment table.
- Update the parser to handle labels and goto. This doesn't check that
gotos are well formed, but at least means the parser doesn't fall
over on them.
- Update our docs to reflect the changes to Cobalt.
This tries to cover some holes in our existing coverage.
- Port some of our Java readable handle tests to Lua (and also clean up
the Java versions to stop using ObjectWrapper - that dates to
pre-@LuaFunction!)
- Test a couple of discrepancies between binary and text handles. This
is mostly to do with the original number-based .read() and .write()
interface for binary handles.
- Fix a couple of edge cases in file-size accounting.
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
This is an attempt to enforce better separation between ComputerThread
and ComputerExecutor. Both of these classes are pretty complex in their
own right, and the way the two bleed into each other makes it all the
more confusing!
This effectively splits the ComputerExecutor into two separate classes:
- ComputerScheduler.Executor (with the actual implementation inside
ComputerThread): This holds all the ComputerThread-related logic
which used to be in ComputerExecutor, including:
- before/after work hooks
- is-on-thread tracking
- virtual runtime computation
- ComputerScheduler.Worker: This encapsulates all the computer-related
behaviour. The actual implementation remains in ComputerExecutor.
The boundaries are still a little fuzzy here, and it's all definitely
more coupled then I'd like, but still an improvement!
There are several additional changes at the same time:
- TimeoutState has also been split up, to better define the boundary
between consumers (such as ComputerExecutor and ILuaMachine) and
controllers (ComputerThread).
The getters still live in TimeoutState, but the core logic lives in
ManagedTimeoutState.
- We no longer track cumulative time in the TimeoutState. Instead, we
allow varying the timeout of a computer. When a computer is paused,
we store the remaining time, and restore it when resuming again.
This also allows us give a longer timeout for computer
startup/shutdown, hopefully avoiding some of those class-not-found
issues we've seen.
- We try to make the state machine of how ComputerExecutors live on the
queue a little more explicit. This is very messy/confusing -
something I want to property test in the future.
I'm sure there's more to be done here, especially in ComputerExecutor,
but hopefully this makes future changes a little less intimidating.
This is the second time I've rewritten our class generation in a little
over a month. Oh dear!
Back in d562a051c7 we started using method
handles inside our generated ASM, effectively replacing a direct call
with .invokeExact on a constant method handle.
This goes one step further and removes our ASM entirely, building up a
MethodHandle that checks arguments and then wraps the return value.
Rather than generating a class, we just return a new LuaFunction
instance that invokeExacts the method handle.
This is definitely slower than what we had before, but in the order of
8ns vs 12ns (in the worst case, sometimes they're much more comparable),
so I'm not too worried in practice.
However, generation of the actual method is now a bit faster. I've not
done any proper benchmarking, but it's about 20-30% faster.
This also gives us a bit more flexibility in the future, for instance
uisng bound MethodHandles in generation (e.g. for instance methods on
GenericSources). Not something I'm planning on doing right now, but is
an option.
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.
This should be significantly faster than LoadingCache (2.5x in my
benchmarks, but not sure they're representative). This isn't super
important - a lookup only takes 6us - but still worth using!
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.
Paint implements its menu slightly differently to edit, in that it takes
control of the event loop until the menu is closed. This means that the
term_resize event is ignored, and so the canvas not redrawn when the
menu is open.
Historically we've used copy-cat to provide a web-based emulator for
running example code on our documentation site. However, copy-cat is
often out-of-date with CC:T, which means example snippets fail when you
try to run them!
This commit vendors in copy-cat (or rather an updated version of it)
into CC:T itself, allowing us to ensure the emulator is always in sync
with the mod.
While the ARCHITECTURE.md documentation goes into a little bit more
detail here, the general implementation is as follows
- In project/src/main we implement the core of the emulator. This
includes a basic reimplementation of some of CC's classes to work on
the web (mostly the HTTP API and ComputerThread), and some additional
code to expose the computers to Javascript.
- This is all then compiled to Javascript using [TeaVM][1] (we actually
use a [personal fork of it][2] as there's a couple of changes I've
not upstreamed yet).
- The Javascript side then pulls in the these compiled classes (and
the CC ROM) and hooks them up to [cc-web-term][3] to display the
actual computer.
- As we're no longer pulling in copy-cat, we can simplify our bundling
system a little - we now just compile to ESM modules directly.
[1]: https://github.com/konsoletyper/teavm
[2]: https://github.com/SquidDev/teavm/tree/squid-patches
[3]: https://github.com/squiddev-cc/cc-web-term
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.
- 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.
- Add a new WebsocketClient interface, which WebsocketHandle uses for
sending messages and closing. This reduces coupling between Websocket
and WebsocketHandle, which is nice, though admitedly only use for
copy-cat :).
- WebsocketHandle now uses Websocket(Client).isClosed(), rather than
tracking the closed state itself - this makes the class mostly a thin
Lua wrapper over the client, which is nice.
- Convert Options into a record.
- Clarify the behaviour of ws.close() and the websocket_closed event.
Our previous test was incorrect as it called WebsocketHandle.close
(rather than WebsocketHandle.doClose), which had slightly different
semantics in whether the event is queued.
We already use preact for the copy-cat integration, so it makes sense to
use it during the static pass too. This allows us to drop a dependency
on react.
As this is responsible for interrupting computers, we should make sure
its priority is higher than the background threads. It spends most of
its time sleeping, so should be fine.
When the target method is in a different class loader to CC, our
generated method fails, as it cannot find the target class. To get
around that, we create a MethodHandle to the target method, and then
inject that into the generated class (with Java's new dynamic constant
system). We can then invoke the MethodHandle in our generated code,
avoiding any references to the target class/method.