Due to the asynchronous nature of main-thread tasks, it's possible for
them to be executed on peripherals which have been detached. This has
been known for a long time (#893 was opened back in 2021), but finding a
good solution here is tricky.
Most of the time the method will silently succeed, but if we try to
interact with an IComputerAccess (such as in inventory methods, as seen
in #1750), we throw a NotAttachedException exception and spam the logs!
This is an initial step towards fixing this - when calling a peripheral
method via peripheral.call/modem.callRemote, we now wrap any enqueued
main-thread tasks and silently skip them if the peripheral has been
detached since.
This means that peripheral methods may start to return nil when they
didn't before. I think this is *fine* (though not ideal for sure!) - we
return nil if the peripheral has been detached, so it's largely
equivalent to that.
This feels a little overkill, but nice to standardise how this code
looks.
There's a bit of me which wonders if we should remove
IPeripheral.equals, and just use Object.equals, but I do also kinda like
the explicitness of the current interface? IDK.
The original runtime error reporting PR[^1] added a "cc.exception"
module, which allowed coroutine managers (such as parallel) to throw
rich errors, detailing the original context where the error was thrown.
Unfortunately, the change to parallel broke some programs (>_>, don't do
string pattern matching on your errors!), and so had to be reverted,
along with the cc.exception module.
As a minimal replacement for this, we add support for user-thrown
exceptions within our internal code. If an error object "looks" like an
exception ("exception" __name, and a message and thread field), then we
use that as our error information instead.
This is currently undocumented (at least in user-facing documentation),
mostly because I couldn't figure out where to put it - the interface
should remain stable.
[^1]: https://github.com/cc-tweaked/CC-Tweaked/pull/1320
Historically, computers tracked whether any world-visible state
(on/off/blinking, label and redstone outputs) had changed with a single
"has changed" flag. While this is simple to use, this has the curious
side effect of that term.setCursorBlink() or os.setComputerLabel() would
cause a block update!
This isn't really a problem in practice - it just means slightly more
block updates. However, the redstone propagation sometimes causes the
computer to invalidate/recheck peripherals, which masks several other
(yet unfixed) bugs.
- colors.toBlit now performs bounds checks on the passed value,
preventing weird behaviour like color.toBlit(2 ^ 16) returning "10".
- The window API now uses colors.toBlit (or rather a copy of it) for
parsing colours, allowing doing silly things like
term.setTextColour(colours.blue + 5).
- Add some top-level documentation to the term API to explain some of
the basics.
Closes#1736
We've been out-of-date for a while now, as we needed to update
lua_menhir to work with lrgrep 3.
- Better handling of standalone names/expressions - we now correctly
handle lists of names.
- Handle missing commas in tables in a few more places.
- We checked the backing array when reading rather than the file's
length, so could read beyond the end of the file.
- We used the entry length when resizing, which effectively meant we
doubled the size of the backing array on each write.
- cc.require now uses the internal _LOADED table to get the list of
built-in globals. This fixes several globals not showing up on the
list (e.g. utf8), and allows us to inject more modules from the Java
side.
- ILuaAPI now has a getModuleName() function. This is used to inject
the API into the aforementioned _LOADED table, allowing it to be
"require"d.
One common issue we get when a program exits after handling a "key"
event is that it leaves the "char" event on the queue. This means that
the shell (or whatever program we switch in to) then receives the "char"
event, often displaying it to the screen.
Previously we've got around this by doing sleep(0) before exiting the
program. However, we also see this problem in edit's run handler script,
and I'm less comfortable doing the same hack there.
This adds a new internal discard_char function, which will either
wait one tick or return when seeing a char/key_up event.
Fixes#1705
- 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.