diff --git a/build.gradle b/build.gradle index bec2bf643..c8d1e548e 100644 --- a/build.gradle +++ b/build.gradle @@ -174,7 +174,7 @@ dependencies { testModImplementation sourceSets.main.output testModExtra 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.21' - cctJavadoc 'cc.tweaked:cct-javadoc:1.4.4' + cctJavadoc 'cc.tweaked:cct-javadoc:1.4.5' } // Compile tasks diff --git a/doc/events/modem_message.md b/doc/events/modem_message.md index ec619f3d4..c7480d7fb 100644 --- a/doc/events/modem_message.md +++ b/doc/events/modem_message.md @@ -2,7 +2,7 @@ module: [kind=event] modem_message --- -The @{modem_message} event is fired when a message is received on an open channel on any modem. +The @{modem_message} event is fired when a message is received on an open channel on any @{modem}. ## Return Values 1. @{string}: The event name. @@ -10,11 +10,15 @@ The @{modem_message} event is fired when a message is received on an open channe 3. @{number}: The channel that the message was sent on. 4. @{number}: The reply channel set by the sender. 5. @{any}: The message as sent by the sender. -6. @{number}: The distance between the sender and the receiver, in blocks (decimal). +6. @{number}: The distance between the sender and the receiver, in blocks. ## Example -Prints a message when one is sent: +Wraps a @{modem} peripheral, opens channel 0 for listening, and prints all received messages. + ```lua +local modem = peripheral.find("modem") or error("No modem attached", 0) +modem.open(0) + while true do local event, side, channel, replyChannel, message, distance = os.pullEvent("modem_message") print(("Message received on side %s on channel %d (reply to %d) from %f blocks away with message %s"):format(side, channel, replyChannel, distance, tostring(message))) diff --git a/doc/guides/using_require.md b/doc/guides/using_require.md new file mode 100644 index 000000000..06cc2ee20 --- /dev/null +++ b/doc/guides/using_require.md @@ -0,0 +1,83 @@ +--- +module: [kind=guide] using_require +--- + +# Reusing code with require +A library is a collection of useful functions and other definitions which is stored separately to your main program. You +might want to create a library because you have some functions which are used in multiple programs, or just to split +your program into multiple more modular files. + +Let's say we want to create a small library to make working with the @{term|terminal} a little easier. We'll provide two +functions: `reset`, which clears the terminal and sets the cursor to (1, 1), and `write_center`, which prints some text +in the middle of the screen. + +Start off by creating a file called `more_term.lua`: + +```lua {data-snippet=more_term} +local function reset() + term.clear() + term.setCursorPos(1, 1) +end + +local function write_center(text) + local x, y = term.getCursorPos() + local width, height = term.getSize() + term.setCursorPos(math.floor((width - #text) / 2) + 1, y) + term.write(text) +end + +return { reset = reset, write_center = write_center } +``` + +Now, what's going on here? We define our two functions as one might expect, and then at the bottom return a table with +the two functions. When we require this library, this table is what is returned. With that, we can then call the +original functions. Now create a new file, with the following: + +```lua {data-mount=more_term:more_term.lua} +local more_term = require("more_term") +more_term.reset() +more_term.write_center("Hello, world!") +``` + +When run, this'll clear the screen and print some text in the middle of the first line. + +## require in depth +While the previous section is a good introduction to how @{require} operates, there are a couple of remaining points +which are worth mentioning for more advanced usage. + +### Libraries can return anything +In our above example, we return a table containing the functions we want to expose. However, it's worth pointing out +that you can return ''anything'' from your library - a table, a function or even just a string! @{require} treats them +all the same, and just returns whatever your library provides. + +### Module resolution and the package path +In the above examples, we defined our library in a file, and @{require} read from it. While this is what you'll do most +of the time, it is possible to make @{require} look elsewhere for your library, such as downloading from a website or +loading from an in-memory library store. + +As a result, the *module name* you pass to @{require} doesn't correspond to a file path. One common mistake is to load +code from a sub-directory using `require("folder/library")` or even `require("folder/library.lua")`, neither of which +will do quite what you expect. + +When loading libraries (also referred to as *modules*) from files, @{require} searches along the *@{package.path|module +path}*. By default, this looks something like: + +* `?.lua` +* `?/init.lua` +* `/rom/modules/main/?.lua` +* etc... + +When you call `require("my_library")`, @{require} replaces the `?` in each element of the path with your module name, and +checks if the file exists. In this case, we'd look for `my_library.lua`, `my_library/init.lua`, +`/rom/modules/main/my_library.lua` and so on. Note that this works *relative to the current program*, so if your +program is actually called `folder/program`, then we'll look for `folder/my_library.lua`, etc... + +One other caveat is loading libraries from sub-directories. For instance, say we have a file +`my/fancy/library.lua`. This can be loaded by using `require("my.fancy.library")` - the '.'s are replaced with '/' +before we start looking for the library. + +## External links +There are several external resources which go into require in a little more detail: + + - The [Lua Module tutorial](http://lua-users.org/wiki/ModulesTutorial) on the Lua wiki. + - [Lua's manual section on @{require}](https://www.lua.org/manual/5.1/manual.html#pdf-require). diff --git a/doc/stub/global.lua b/doc/stub/global.lua index a2d54fa12..6541da7e5 100644 --- a/doc/stub/global.lua +++ b/doc/stub/global.lua @@ -12,16 +12,19 @@ rounded up to the nearest multiple of 0.05 seconds. If you are using coroutines or the @{parallel|parallel API}, it will only pause execution of the current thread, not the whole program. -**Note** Because sleep internally uses timers, it is a function that yields. -This means that you can use it to prevent "Too long without yielding" errors, -however, as the minimum sleep time is 0.05 seconds, it will slow your program -down. +:::tip +Because sleep internally uses timers, it is a function that yields. This means +that you can use it to prevent "Too long without yielding" errors, however, as +the minimum sleep time is 0.05 seconds, it will slow your program down. +::: -**Warning** Internally, this function queues and waits for a timer event (using +:::caution +Internally, this function queues and waits for a timer event (using @{os.startTimer}), however it does not listen for any other events. This means that any event that occurs while sleeping will be entirely discarded. If you need to receive events while sleeping, consider using @{os.startTimer|timers}, or the @{parallel|parallel API}. +::: @tparam number time The number of seconds to sleep for, rounded up to the nearest multiple of 0.05. diff --git a/gradle.properties b/gradle.properties index a570e7f3e..cc8d5b629 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ org.gradle.jvmargs=-Xmx3G # Mod properties -mod_version=1.99.1 +mod_version=1.100.0 # Minecraft properties (update mods.toml when changing) mc_version=1.17.1 diff --git a/src/main/java/dan200/computercraft/api/lua/LuaTable.java b/src/main/java/dan200/computercraft/api/lua/LuaTable.java index 6bbafe6fd..69a22b9c1 100644 --- a/src/main/java/dan200/computercraft/api/lua/LuaTable.java +++ b/src/main/java/dan200/computercraft/api/lua/LuaTable.java @@ -5,9 +5,8 @@ */ package dan200.computercraft.api.lua; -import org.jetbrains.annotations.Nullable; - import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.Map; import static dan200.computercraft.api.lua.LuaValues.*; diff --git a/src/main/java/dan200/computercraft/client/sound/DfpwmStream.java b/src/main/java/dan200/computercraft/client/sound/DfpwmStream.java index 38c29c851..f967d3d2c 100644 --- a/src/main/java/dan200/computercraft/client/sound/DfpwmStream.java +++ b/src/main/java/dan200/computercraft/client/sound/DfpwmStream.java @@ -115,7 +115,9 @@ class DfpwmStream implements AudioStream } result.flip(); - return result; + + // This is naughty, but ensures we're not enqueuing empty buffers when the stream is exhausted. + return result.remaining() == 0 ? null : result; } @Override @@ -123,4 +125,9 @@ class DfpwmStream implements AudioStream { buffers.clear(); } + + public boolean isEmpty() + { + return buffers.isEmpty(); + } } diff --git a/src/main/java/dan200/computercraft/client/sound/SpeakerInstance.java b/src/main/java/dan200/computercraft/client/sound/SpeakerInstance.java index 039ab3888..e5fcb0096 100644 --- a/src/main/java/dan200/computercraft/client/sound/SpeakerInstance.java +++ b/src/main/java/dan200/computercraft/client/sound/SpeakerInstance.java @@ -27,8 +27,20 @@ public class SpeakerInstance public synchronized void pushAudio( ByteBuf buffer ) { - if( currentStream == null ) currentStream = new DfpwmStream(); + SpeakerSound sound = this.sound; + + DfpwmStream stream = currentStream; + if( stream == null ) stream = currentStream = new DfpwmStream(); + boolean exhausted = stream.isEmpty(); currentStream.push( buffer ); + + // If we've got nothing left in the buffer, enqueue an additional one just in case. + if( exhausted && sound != null && sound.stream == stream && sound.channel != null ) + { + sound.executor.execute( () -> { + if( !sound.channel.stopped() ) sound.channel.pumpBuffers( 1 ); + } ); + } } public void playAudio( Vec3 position, float volume ) diff --git a/src/main/java/dan200/computercraft/client/sound/SpeakerManager.java b/src/main/java/dan200/computercraft/client/sound/SpeakerManager.java index 7ab6ac612..62b4713c8 100644 --- a/src/main/java/dan200/computercraft/client/sound/SpeakerManager.java +++ b/src/main/java/dan200/computercraft/client/sound/SpeakerManager.java @@ -26,12 +26,14 @@ public class SpeakerManager @SubscribeEvent public static void playStreaming( PlayStreamingSourceEvent event ) { - if( !(event.getSound() instanceof SpeakerSound) ) return; - SpeakerSound sound = (SpeakerSound) event.getSound(); + if( !(event.getSound() instanceof SpeakerSound sound) ) return; if( sound.stream == null ) return; event.getSource().attachBufferStream( sound.stream ); event.getSource().play(); + + sound.channel = event.getSource(); + sound.executor = event.getManager().executor; } public static SpeakerInstance getSound( UUID source ) diff --git a/src/main/java/dan200/computercraft/client/sound/SpeakerSound.java b/src/main/java/dan200/computercraft/client/sound/SpeakerSound.java index 20dcc27a0..d1de9286e 100644 --- a/src/main/java/dan200/computercraft/client/sound/SpeakerSound.java +++ b/src/main/java/dan200/computercraft/client/sound/SpeakerSound.java @@ -5,6 +5,7 @@ */ package dan200.computercraft.client.sound; +import com.mojang.blaze3d.audio.Channel; import net.minecraft.client.resources.sounds.AbstractSoundInstance; import net.minecraft.client.resources.sounds.TickableSoundInstance; import net.minecraft.client.sounds.AudioStream; @@ -13,9 +14,12 @@ import net.minecraft.sounds.SoundSource; import net.minecraft.world.phys.Vec3; import javax.annotation.Nullable; +import java.util.concurrent.Executor; public class SpeakerSound extends AbstractSoundInstance implements TickableSoundInstance { + Channel channel; + Executor executor; DfpwmStream stream; SpeakerSound( ResourceLocation sound, DfpwmStream stream, Vec3 position, float volume, float pitch ) diff --git a/src/main/java/dan200/computercraft/core/apis/FSAPI.java b/src/main/java/dan200/computercraft/core/apis/FSAPI.java index 6c3fd3522..550c23397 100644 --- a/src/main/java/dan200/computercraft/core/apis/FSAPI.java +++ b/src/main/java/dan200/computercraft/core/apis/FSAPI.java @@ -30,7 +30,36 @@ import java.util.OptionalLong; import java.util.function.Function; /** - * The FS API allows you to manipulate files and the filesystem. + * The FS API provides access to the computer's files and filesystem, allowing you to manipulate files, directories and + * paths. This includes: + * + * + * + * :::note + * All functions in the API work on absolute paths, and do not take the @{shell.dir|current directory} into account. + * You can use @{shell.resolve} to convert a relative path into an absolute one. + * ::: + * + * ## Mounts + * While a computer can only have one hard drive and filesystem, other filesystems may be "mounted" inside it. For + * instance, the {@link dan200.computercraft.shared.peripheral.diskdrive.DiskDrivePeripheral drive peripheral} mounts + * its disk's contents at {@code "disk/"}, {@code "disk1/"}, etc... + * + * You can see which mount a path belongs to with the {@link #getDrive} function. This returns {@code "hdd"} for the + * computer's main filesystem ({@code "/"}), {@code "rom"} for the rom ({@code "rom/"}). + * + * Most filesystems have a limited capacity, operations which would cause that capacity to be reached (such as writing + * an incredibly large file) will fail. You can see a mount's capacity with {@link #getCapacity} and the remaining + * space with {@link #getFreeSpace}. * * @cc.module fs */ @@ -440,6 +469,12 @@ public class FSAPI implements ILuaAPI * @return The name of the drive that the file is on; e.g. {@code hdd} for local files, or {@code rom} for ROM files. * @throws LuaException If the path doesn't exist. * @cc.treturn string The name of the drive that the file is on; e.g. {@code hdd} for local files, or {@code rom} for ROM files. + * @cc.usage Print the drives of a couple of mounts: + * + *
{@code
+     * print("/: " .. fs.getDrive("/"))
+     * print("/rom/: " .. fs.getDrive("rom"))
+     * }
*/ @LuaFunction public final Object[] getDrive( String path ) throws LuaException diff --git a/src/main/java/dan200/computercraft/core/apis/RedstoneAPI.java b/src/main/java/dan200/computercraft/core/apis/RedstoneAPI.java index 3fc1285d4..ab4ff41a6 100644 --- a/src/main/java/dan200/computercraft/core/apis/RedstoneAPI.java +++ b/src/main/java/dan200/computercraft/core/apis/RedstoneAPI.java @@ -22,7 +22,7 @@ import dan200.computercraft.core.computer.ComputerSide; * as those from Project:Red. These allow you to send 16 separate on/off signals. Each channel corresponds to a * colour, with the first being @{colors.white} and the last @{colors.black}. * - * Whenever a redstone input changes, a {@code redstone} event will be fired. This may be used instead of repeativly + * Whenever a redstone input changes, a @{event!redstone} event will be fired. This may be used instead of repeativly * polling. * * This module may also be referred to as {@code rs}. For example, one may call {@code rs.getSides()} instead of diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/EnergyMethods.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/EnergyMethods.java index e8121f600..30afeb56f 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/EnergyMethods.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/EnergyMethods.java @@ -19,10 +19,10 @@ import javax.annotation.Nonnull; * * This works with energy storage blocks, as well as generators and machines which consume energy. * - *
- * Note: Due to limitations with Forge's energy API, it is not possible to measure throughput (i.e. RF + * :::note + * Due to limitations with Forge's energy API, it is not possible to measure throughput (i.e. RF * used/generated per tick). - *
+ * ::: * * @cc.module energy_storage */ diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/ModemPeripheral.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/ModemPeripheral.java index e312c43ff..b2e8f45f3 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/modem/ModemPeripheral.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/ModemPeripheral.java @@ -21,9 +21,60 @@ import java.util.HashSet; import java.util.Set; /** - * The modem peripheral allows you to send messages between computers. + * Modems allow you to send messages between computers over long distances. + * + * :::tip + * Modems provide a fairly basic set of methods, which makes them very flexible but often hard to work with. The + * {@literal @}{rednet} API is built on top of modems, and provides a more user-friendly interface. + * ::: + * + * ## Sending and receiving messages + * Modems operate on a series of channels, a bit like frequencies on a radio. Any modem can send a message on a + * particular channel, but only those which have {@link #open opened} the channel and are "listening in" can receive + * messages. + * + * Channels are represented as an integer between 0 and 65535 inclusive. These channels don't have any defined meaning, + * though some APIs or programs will assign a meaning to them. For instance, the @{gps} module sends all its messages on + * channel 65534 (@{gps.CHANNEL_GPS}), while @{rednet} uses channels equal to the computer's ID. + * + * - Sending messages is done with the {@link #transmit(int, int, Object)} message. + * - Receiving messages is done by listening to the @{modem_message} event. + * + * ## Types of modem + * CC: Tweaked comes with three kinds of modem, with different capabilities. + * + * * * @cc.module modem + * @cc.see modem_message Queued when a modem receives a message on an {@link #open(int) open channel}. + * @cc.see rednet A networking API built on top of the modem peripheral. + * @cc.usage Wrap a modem and a message on channel 15, requesting a response on channel 43. Then wait for a message to + * arrive on channel 43 and print it. + * + *
{@code
+ * local modem = peripheral.find("modem") or error("No modem attached", 0)
+ * modem.open(43) -- Open 43 so we can receive replies
+ *
+ * -- Send our message
+ * modem.transmit(15, 43, "Hello, world!")
+ *
+ * -- And wait for a reply
+ * local event, side, channel, replyChannel, message, distance
+ * repeat
+ *   event, side, channel, replyChannel, message, distance = os.pullEvent("modem_message")
+ * until channel == 43
+ *
+ * print("Received a reply: " .. tostring(message))
+ * }
*/ public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPacketReceiver { @@ -157,12 +208,23 @@ public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPa * Sends a modem message on a certain channel. Modems listening on the channel will queue a {@code modem_message} * event on adjacent computers. * - *
Note: The channel does not need be open to send a message.
+ * :::note + * The channel does not need be open to send a message. + * ::: * * @param channel The channel to send messages on. - * @param replyChannel The channel that responses to this message should be sent on. - * @param payload The object to send. This can be a boolean, string, number, or table. + * @param replyChannel The channel that responses to this message should be sent on. This can be the same as + * {@code channel} or entirely different. The channel must have been {@link #open opened} on + * the sending computer in order to receive the replies. + * @param payload The object to send. This can be any primitive type (boolean, number, string) as well as + * tables. Other types (like functions), as well as metatables, will not be transmitted. * @throws LuaException If the channel is out of range. + * @cc.usage Wrap a modem and a message on channel 15, requesting a response on channel 43. + * + *
{@code
+     * local modem = peripheral.find("modem") or error("No modem attached", 0)
+     * modem.transmit(15, 43, "Hello, world!")
+     * }
*/ @LuaFunction public final void transmit( int channel, int replyChannel, Object payload ) throws LuaException diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemPeripheral.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemPeripheral.java index 5ff939590..6340206d7 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemPeripheral.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemPeripheral.java @@ -80,8 +80,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW * If this computer is attached to the network, it _will not_ be included in * this list. * - *
Important: This function only appears on wired modems. Check {@link #isWireless} - * returns false before calling it.
+ * :::note + * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it. + * ::: * * @param computer The calling computer. * @return Remote peripheral names on the network. @@ -95,8 +96,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW /** * Determine if a peripheral is available on this wired network. * - *
Important: This function only appears on wired modems. Check {@link #isWireless} - * returns false before calling it.
+ * :::note + * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it. + * ::: * * @param computer The calling computer. * @param name The peripheral's name. @@ -112,8 +114,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW /** * Get the type of a peripheral is available on this wired network. * - *
Important: This function only appears on wired modems. Check {@link #isWireless} - * returns false before calling it.
+ * :::note + * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it. + * ::: * * @param computer The calling computer. * @param name The peripheral's name. @@ -132,8 +135,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW /** * Check a peripheral is of a particular type. * - *
Important: This function only appears on wired modems. Check {@link #isWireless} - * returns false before calling it.
+ * :::note + * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it. + * ::: * * @param computer The calling computer. * @param name The peripheral's name. @@ -153,8 +157,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW /** * Get all available methods for the remote peripheral with the given name. * - *
Important: This function only appears on wired modems. Check {@link #isWireless} - * returns false before calling it.
+ * :::note + * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it. + * ::: * * @param computer The calling computer. * @param name The peripheral's name. @@ -174,8 +179,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW /** * Call a method on a peripheral on this wired network. * - *
Important: This function only appears on wired modems. Check {@link #isWireless} - * returns false before calling it.
+ * :::note + * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it. + * ::: * * @param computer The calling computer. * @param context The Lua context we're executing in. @@ -204,8 +210,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW * may be used by other computers on the network to wrap this computer as a * peripheral. * - *
Important: This function only appears on wired modems. Check {@link #isWireless} - * returns false before calling it.
+ * :::note + * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it. + * ::: * * @return The current computer's name. * @cc.treturn string|nil The current computer's name on the wired network. diff --git a/src/main/java/dan200/computercraft/shared/peripheral/speaker/DfpwmState.java b/src/main/java/dan200/computercraft/shared/peripheral/speaker/DfpwmState.java index 2f95be839..f7df63484 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/speaker/DfpwmState.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/speaker/DfpwmState.java @@ -7,6 +7,7 @@ package dan200.computercraft.shared.peripheral.speaker; import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaTable; +import dan200.computercraft.shared.util.PauseAwareTimer; import net.minecraft.util.Mth; import javax.annotation.Nonnull; @@ -27,7 +28,7 @@ class DfpwmState * The minimum size of the client's audio buffer. Once we have less than this on the client, we should send another * batch of audio. */ - private static final long CLIENT_BUFFER = (long) (SECOND * 1.5); + private static final long CLIENT_BUFFER = (long) (SECOND * 0.5); private static final int PREC = 10; @@ -36,7 +37,7 @@ class DfpwmState private boolean previousBit = false; private boolean unplayed = true; - private long clientEndTime = System.nanoTime(); + private long clientEndTime = PauseAwareTimer.getTime(); private float pendingVolume = 1.0f; private ByteBuffer pendingAudio; @@ -107,7 +108,7 @@ class DfpwmState boolean isPlaying() { - return unplayed || clientEndTime >= System.nanoTime(); + return unplayed || clientEndTime >= PauseAwareTimer.getTime(); } float getVolume() diff --git a/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java b/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java index c6764e430..eb3ee3dd1 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java @@ -17,6 +17,7 @@ import dan200.computercraft.shared.network.client.SpeakerAudioClientMessage; import dan200.computercraft.shared.network.client.SpeakerMoveClientMessage; import dan200.computercraft.shared.network.client.SpeakerPlayClientMessage; import dan200.computercraft.shared.network.client.SpeakerStopClientMessage; +import dan200.computercraft.shared.util.PauseAwareTimer; import net.minecraft.ResourceLocationException; import net.minecraft.core.BlockPos; import net.minecraft.network.protocol.game.ClientboundCustomSoundPacket; @@ -119,7 +120,7 @@ public abstract class SpeakerPeripheral implements IPeripheral return; } - long now = System.nanoTime(); + long now = PauseAwareTimer.getTime(); if( sound != null ) { lastPlayTime = clock; @@ -342,7 +343,7 @@ public abstract class SpeakerPeripheral implements IPeripheral // TODO: Use ArgumentHelpers instead? int length = audio.length(); if( length <= 0 ) throw new LuaException( "Cannot play empty audio" ); - if( length > 1024 * 16 * 8 ) throw new LuaException( "Audio data is too large" ); + if( length > 128 * 1024 ) throw new LuaException( "Audio data is too large" ); DfpwmState state; synchronized( lock ) @@ -387,17 +388,7 @@ public abstract class SpeakerPeripheral implements IPeripheral computers.remove( computer ); } - private static final class PendingSound + private record PendingSound(ResourceLocation location, float volume, float pitch) { - final ResourceLocation location; - final float volume; - final float pitch; - - private PendingSound( ResourceLocation location, float volume, float pitch ) - { - this.location = location; - this.volume = volume; - this.pitch = pitch; - } } } diff --git a/src/main/java/dan200/computercraft/shared/pocket/items/ItemPocketComputer.java b/src/main/java/dan200/computercraft/shared/pocket/items/ItemPocketComputer.java index 77360798c..6bcfe0490 100644 --- a/src/main/java/dan200/computercraft/shared/pocket/items/ItemPocketComputer.java +++ b/src/main/java/dan200/computercraft/shared/pocket/items/ItemPocketComputer.java @@ -17,7 +17,6 @@ import dan200.computercraft.shared.common.IColouredItem; import dan200.computercraft.shared.computer.core.ClientComputer; import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.core.ComputerState; -import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.computer.items.IComputerItem; import dan200.computercraft.shared.network.container.ComputerContainerData; import dan200.computercraft.shared.pocket.apis.PocketAPI; @@ -34,6 +33,7 @@ import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResultHolder; import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.Item; @@ -80,53 +80,65 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I PocketUpgrades.getVanillaUpgrades().map( x -> create( -1, null, -1, x ) ).forEach( stacks::add ); } + private boolean tick( @Nonnull ItemStack stack, @Nonnull Level world, @Nonnull Entity entity, @Nonnull PocketServerComputer computer ) + { + IPocketUpgrade upgrade = getUpgrade( stack ); + + computer.setLevel( world ); + computer.updateValues( entity, stack, upgrade ); + + boolean changed = false; + + // Sync ID + int id = computer.getID(); + if( id != getComputerID( stack ) ) + { + changed = true; + setComputerID( stack, id ); + } + + // Sync label + String label = computer.getLabel(); + if( !Objects.equal( label, getLabel( stack ) ) ) + { + changed = true; + setLabel( stack, label ); + } + + // Update pocket upgrade + if( upgrade != null ) upgrade.update( computer, computer.getPeripheral( ComputerSide.BACK ) ); + + return changed; + } + @Override public void inventoryTick( @Nonnull ItemStack stack, Level world, @Nonnull Entity entity, int slotNum, boolean selected ) { if( !world.isClientSide ) { - // Server side - Container inventory = entity instanceof Player ? ((Player) entity).getInventory() : null; - PocketServerComputer computer = createServerComputer( world, inventory, entity, stack ); - if( computer != null ) - { - IPocketUpgrade upgrade = getUpgrade( stack ); + Container inventory = entity instanceof Player player ? player.getInventory() : null; + PocketServerComputer computer = createServerComputer( world, entity, inventory, stack ); + computer.keepAlive(); - // Ping computer - computer.keepAlive(); - computer.setLevel( world ); - computer.updateValues( entity, stack, upgrade ); - - // Sync ID - int id = computer.getID(); - if( id != getComputerID( stack ) ) - { - setComputerID( stack, id ); - if( inventory != null ) inventory.setChanged(); - } - - // Sync label - String label = computer.getLabel(); - if( !Objects.equal( label, getLabel( stack ) ) ) - { - setLabel( stack, label ); - if( inventory != null ) inventory.setChanged(); - } - - // Update pocket upgrade - if( upgrade != null ) - { - upgrade.update( computer, computer.getPeripheral( ComputerSide.BACK ) ); - } - } + boolean changed = tick( stack, world, entity, computer ); + if( changed && inventory != null ) inventory.setChanged(); } else { - // Client side createClientComputer( stack ); } } + @Override + public boolean onEntityItemUpdate( ItemStack stack, ItemEntity entity ) + { + if( entity.level.isClientSide ) return false; + + PocketServerComputer computer = getServerComputer( stack ); + if( computer != null && tick( stack, entity.level, entity, computer ) ) entity.setItem( stack.copy() ); + return false; + } + @Nonnull @Override public InteractionResultHolder use( Level world, Player player, @Nonnull InteractionHand hand ) @@ -134,22 +146,18 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I ItemStack stack = player.getItemInHand( hand ); if( !world.isClientSide ) { - PocketServerComputer computer = createServerComputer( world, player.getInventory(), player, stack ); + PocketServerComputer computer = createServerComputer( world, player, player.getInventory(), stack ); + computer.turnOn(); boolean stop = false; - if( computer != null ) + IPocketUpgrade upgrade = getUpgrade( stack ); + if( upgrade != null ) { - computer.turnOn(); - - IPocketUpgrade upgrade = getUpgrade( stack ); - if( upgrade != null ) - { - computer.updateValues( player, stack, upgrade ); - stop = upgrade.onRightClick( world, computer, computer.getPeripheral( ComputerSide.BACK ) ); - } + computer.updateValues( player, stack, upgrade ); + stop = upgrade.onRightClick( world, computer, computer.getPeripheral( ComputerSide.BACK ) ); } - if( !stop && computer != null ) + if( !stop ) { boolean isTypingOnly = hand == InteractionHand.OFF_HAND; new ComputerContainerData( computer ).open( player, new PocketComputerMenuProvider( computer, stack, this, hand, isTypingOnly ) ); @@ -207,17 +215,17 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I return super.getCreatorModId( stack ); } - public PocketServerComputer createServerComputer( final Level world, Container inventory, Entity entity, @Nonnull ItemStack stack ) + @Nonnull + public PocketServerComputer createServerComputer( Level world, Entity entity, @Nullable Container inventory, @Nonnull ItemStack stack ) { - if( world.isClientSide ) return null; + if( world.isClientSide ) throw new IllegalStateException( "Cannot call createServerComputer on the client" ); PocketServerComputer computer; int instanceID = getInstanceID( stack ); int sessionID = getSessionID( stack ); int correctSessionID = ComputerCraft.serverComputerRegistry.getSessionID(); - if( instanceID >= 0 && sessionID == correctSessionID && - ComputerCraft.serverComputerRegistry.contains( instanceID ) ) + if( instanceID >= 0 && sessionID == correctSessionID && ComputerCraft.serverComputerRegistry.contains( instanceID ) ) { computer = (PocketServerComputer) ComputerCraft.serverComputerRegistry.get( instanceID ); } @@ -235,13 +243,7 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I computerID = ComputerCraftAPI.createUniqueNumberedSaveDir( world, "computer" ); setComputerID( stack, computerID ); } - computer = new PocketServerComputer( - world, - computerID, - getLabel( stack ), - instanceID, - getFamily() - ); + computer = new PocketServerComputer( world, computerID, getLabel( stack ), instanceID, getFamily() ); computer.updateValues( entity, stack, getUpgrade( stack ) ); computer.addAPI( new PocketAPI( computer ) ); ComputerCraft.serverComputerRegistry.add( instanceID, computer ); @@ -251,15 +253,17 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I return computer; } - public static ServerComputer getServerComputer( @Nonnull ItemStack stack ) + @Nullable + public static PocketServerComputer getServerComputer( @Nonnull ItemStack stack ) { int session = getSessionID( stack ); if( session != ComputerCraft.serverComputerRegistry.getSessionID() ) return null; int instanceID = getInstanceID( stack ); - return instanceID >= 0 ? ComputerCraft.serverComputerRegistry.get( instanceID ) : null; + return instanceID >= 0 ? (PocketServerComputer) ComputerCraft.serverComputerRegistry.get( instanceID ) : null; } + @Nullable public static ClientComputer createClientComputer( @Nonnull ItemStack stack ) { int instanceID = getInstanceID( stack ); @@ -274,6 +278,7 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I return null; } + @Nullable private static ClientComputer getClientComputer( @Nonnull ItemStack stack ) { int instanceID = getInstanceID( stack ); diff --git a/src/main/java/dan200/computercraft/shared/turtle/apis/TurtleAPI.java b/src/main/java/dan200/computercraft/shared/turtle/apis/TurtleAPI.java index 653176016..5fa5777c7 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/apis/TurtleAPI.java +++ b/src/main/java/dan200/computercraft/shared/turtle/apis/TurtleAPI.java @@ -22,8 +22,50 @@ import java.util.Map; import java.util.Optional; /** - * The turtle API allows you to control your turtle. + * Turtles are a robotic device, which can break and place blocks, attack mobs, and move about the world. They have + * an internal inventory of 16 slots, allowing them to store blocks they have broken or would like to place. * + * ## Movement + * Turtles are capable of moving throug the world. As turtles are blocks themselves, they are confined to Minecraft's + * grid, moving a single block at a time. + * + * {@literal @}{turtle.forward} and @{turtle.back} move the turtle in the direction it is facing, while @{turtle.up} and + * {@literal @}{turtle.down} move it up and down (as one might expect!). In order to move left or right, you first need + * to turn the turtle using @{turtle.turnLeft}/@{turtle.turnRight} and then move forward or backwards. + * + * :::info + * The name "turtle" comes from [Turtle graphics], which originated from the Logo programming language. Here you'd move + * a turtle with various commands like "move 10" and "turn left", much like ComputerCraft's turtles! + * ::: + * + * Moving a turtle (though not turning it) consumes *fuel*. If a turtle does not have any @{turtle.refuel|fuel}, it + * won't move, and the movement functions will return @{false}. If your turtle isn't going anywhere, the first thing to + * check is if you've fuelled your turtle. + * + * :::tip Handling errors + * Many turtle functions can fail in various ways. For instance, a turtle cannot move forward if there's already a block + * there. Instead of erroring, functions which can fail either return @{true} if they succeed, or @{false} and some + * error message if they fail. + * + * Unexpected failures can often lead to strange behaviour. It's often a good idea to check the return values of these + * functions, or wrap them in @{assert} (for instance, use `assert(turtle.forward())` rather than `turtle.forward()`), + * so the program doesn't misbehave. + * ::: + * + * ## Turtle upgrades + * While a normal turtle can move about the world and place blocks, its functionality is limited. Thankfully, turtles + * can be upgraded with *tools* and @{peripheral|peripherals}. Turtles have two upgrade slots, one on the left and right + * sides. Upgrades can be equipped by crafting a turtle with the upgrade, or calling the @{turtle.equipLeft}/@{turtle.equipRight} + * functions. + * + * Turtle tools allow you to break blocks (@{turtle.dig}) and attack entities (@{turtle.attack}). Some tools are more + * suitable to a task than others. For instance, a diamond pickaxe can break every block, while a sword does more + * damage. Other tools have more niche use-cases, for instance hoes can til dirt. + * + * Peripherals (such as the @{modem|wireless modem} or @{speaker}) can also be equipped as upgrades. These are then + * accessible by accessing the `"left"` or `"right"` peripheral. + * + * [Turtle Graphics]: https://en.wikipedia.org/wiki/Turtle_graphics "Turtle graphics" * @cc.module turtle * @cc.since 1.3 */ diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java index 8ab7dd576..80dddafec 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java @@ -31,7 +31,6 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.FluidTags; import net.minecraft.world.Container; import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.EntitySelector; import net.minecraft.world.entity.MoverType; import net.minecraft.world.item.DyeColor; import net.minecraft.world.level.Level; @@ -39,6 +38,7 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.PushReaction; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; import net.minecraftforge.items.IItemHandlerModifiable; @@ -48,6 +48,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.*; import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; import static dan200.computercraft.shared.common.IColouredItem.NBT_COLOUR; import static dan200.computercraft.shared.util.WaterloggableHelpers.WATERLOGGED; @@ -65,6 +66,8 @@ public class TurtleBrain implements ITurtleAccess private static final int ANIM_DURATION = 8; + public static final Predicate PUSHABLE_ENTITY = entity -> !entity.isSpectator() && entity.getPistonPushReaction() != PushReaction.IGNORE; + private TileTurtle owner; private ComputerProxy proxy; private GameProfile owningPlayer; @@ -886,7 +889,7 @@ public class TurtleBrain implements ITurtleAccess } AABB aabb = new AABB( minX, minY, minZ, maxX, maxY, maxZ ); - List list = world.getEntitiesOfClass( Entity.class, aabb, EntitySelector.NO_SPECTATORS ); + List list = world.getEntitiesOfClass( Entity.class, aabb, PUSHABLE_ENTITY ); if( !list.isEmpty() ) { double pushStep = 1.0f / ANIM_DURATION; diff --git a/src/main/java/dan200/computercraft/shared/util/PauseAwareTimer.java b/src/main/java/dan200/computercraft/shared/util/PauseAwareTimer.java new file mode 100644 index 000000000..9c39d5942 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/util/PauseAwareTimer.java @@ -0,0 +1,53 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.shared.util; + +import net.minecraft.Util; +import net.minecraft.client.Minecraft; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +/** + * A monotonically increasing clock which accounts for the game being paused. + */ +@Mod.EventBusSubscriber( Dist.CLIENT ) +public final class PauseAwareTimer +{ + private static boolean paused; + private static long pauseTime; + private static long pauseOffset; + + private PauseAwareTimer() + { + } + + public static long getTime() + { + return (paused ? pauseTime : Util.getNanos()) - pauseOffset; + } + + @SubscribeEvent + public static void tick( TickEvent.RenderTickEvent event ) + { + if( event.phase != TickEvent.Phase.START ) return; + + boolean isPaused = Minecraft.getInstance().isPaused(); + if( isPaused == paused ) return; + + if( isPaused ) + { + pauseTime = Util.getNanos(); + paused = true; + } + else + { + pauseOffset += Util.getNanos() - pauseTime; + paused = false; + } + } +} diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 8b29d2889..ed0ab7b05 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -7,3 +7,7 @@ public net.minecraft.client.gui.components.ChatComponent m_93787_(Lnet/minecraft public net.minecraft.client.gui.components.ChatComponent m_93803_(I)V # removeById # NoTermComputerScreen public net.minecraft.client.Minecraft f_91080_ # screen + +# SpeakerInstance/SpeakerManager +public com.mojang.blaze3d.audio.Channel m_83652_(I)V # pumpBuffers +public net.minecraft.client.sounds.SoundEngine f_120223_ # executor diff --git a/src/main/resources/data/computercraft/lua/rom/apis/disk.lua b/src/main/resources/data/computercraft/lua/rom/apis/disk.lua index 00d691ccc..53fc56dbc 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/disk.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/disk.lua @@ -1,16 +1,18 @@ ---- The Disk API allows you to interact with disk drives. --- --- These functions can operate on locally attached or remote disk drives. To use --- a locally attached drive, specify “side” as one of the six sides --- (e.g. `left`); to use a remote disk drive, specify its name as printed when --- enabling its modem (e.g. `drive_0`). --- --- **Note:** All computers (except command computers), turtles and pocket --- computers can be placed within a disk drive to access it's internal storage --- like a disk. --- --- @module disk --- @since 1.2 +--[[- The Disk API allows you to interact with disk drives. + +These functions can operate on locally attached or remote disk drives. To use a +locally attached drive, specify “side” as one of the six sides (e.g. `left`); to +use a remote disk drive, specify its name as printed when enabling its modem +(e.g. `drive_0`). + +:::tip +All computers (except command computers), turtles and pocket computers can be +placed within a disk drive to access it's internal storage like a disk. +::: + +@module disk +@since 1.2 +]] local function isDrive(name) if type(name) ~= "string" then diff --git a/src/main/resources/data/computercraft/lua/rom/apis/gps.lua b/src/main/resources/data/computercraft/lua/rom/apis/gps.lua index f653166ca..e6f6ea2f8 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/gps.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/gps.lua @@ -1,27 +1,30 @@ ---- The GPS API provides a method for turtles and computers to retrieve their --- own locations. --- --- It broadcasts a PING message over @{rednet} and wait for responses. In order --- for this system to work, there must be at least 4 computers used as gps hosts --- which will respond and allow trilateration. Three of these hosts should be in --- a plane, and the fourth should be either above or below the other three. The --- three in a plane should not be in a line with each other. You can set up --- hosts using the gps program. --- --- **Note**: When entering in the coordinates for the host you need to put in --- the `x`, `y`, and `z` coordinates of the computer, not the modem, as all --- rednet distances are measured from the block the computer is in. --- --- Also note that you may choose which axes x, y, or z refers to - so long as --- your systems have the same definition as any GPS servers that're in range, it --- works just the same. For example, you might build a GPS cluster according to --- [this tutorial][1], using z to account for height, or you might use y to --- account for height in the way that Minecraft's debug screen displays. --- --- [1]: http://www.computercraft.info/forums2/index.php?/topic/3088-how-to-guide-gps-global-position-system/ --- --- @module gps --- @since 1.31 +--[[- The GPS API provides a method for turtles and computers to retrieve their +own locations. + +It broadcasts a PING message over @{rednet} and wait for responses. In order for +this system to work, there must be at least 4 computers used as gps hosts which +will respond and allow trilateration. Three of these hosts should be in a plane, +and the fourth should be either above or below the other three. The three in a +plane should not be in a line with each other. You can set up hosts using the +gps program. + +:::note +When entering in the coordinates for the host you need to put in the `x`, `y`, +and `z` coordinates of the computer, not the modem, as all modem distances are +measured from the block the computer is in. +::: + +Also note that you may choose which axes x, y, or z refers to - so long as your +systems have the same definition as any GPS servers that're in range, it works +just the same. For example, you might build a GPS cluster according to [this +tutorial][1], using z to account for height, or you might use y to account for +height in the way that Minecraft's debug screen displays. + +[1]: http://www.computercraft.info/forums2/index.php?/topic/3088-how-to-guide-gps-global-position-system/ + +@module gps +@since 1.31 +]] local expect = dofile("rom/modules/main/cc/expect.lua").expect diff --git a/src/main/resources/data/computercraft/lua/rom/apis/parallel.lua b/src/main/resources/data/computercraft/lua/rom/apis/parallel.lua index 78e5abdd8..0fe670c08 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/parallel.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/parallel.lua @@ -2,13 +2,13 @@ Functions are not actually executed simultaniously, but rather this API will automatically switch between them whenever they yield (eg whenever they call -@{coroutine.yield}, or functions that call that - eg `os.pullEvent` - or +@{coroutine.yield}, or functions that call that - eg @{os.pullEvent} - or functions that call that, etc - basically, anything that causes the function to "pause"). Each function executed in "parallel" gets its own copy of the event queue, and so "event consuming" functions (again, mostly anything that causes the -script to pause - eg `sleep`, `rednet.receive`, most of the `turtle` API, +script to pause - eg @{os.sleep}, @{rednet.receive}, most of the @{turtle} API, etc) can safely be used in one without affecting the event queue accessed by the other. diff --git a/src/main/resources/data/computercraft/lua/rom/apis/peripheral.lua b/src/main/resources/data/computercraft/lua/rom/apis/peripheral.lua index cb12e6da6..8e8d27a6f 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/peripheral.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/peripheral.lua @@ -66,7 +66,7 @@ type. What is a peripheral type though? This is a string which describes what a peripheral is, and so what functions are available on it. For instance, speakers are just called `"speaker"`, and monitors `"monitor"`. Some peripherals might -have more than one type; a Minecraft chest is both a `"minecraft:chest"` and +have more than one type - a Minecraft chest is both a `"minecraft:chest"` and `"inventory"`. You can get all the types a peripheral has with @{peripheral.getType}, and check diff --git a/src/main/resources/data/computercraft/lua/rom/apis/rednet.lua b/src/main/resources/data/computercraft/lua/rom/apis/rednet.lua index 3bc55a936..3b30bbfdc 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/rednet.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/rednet.lua @@ -1,21 +1,48 @@ ---- The Rednet API allows systems to communicate between each other without --- using redstone. It serves as a wrapper for the modem API, offering ease of --- functionality (particularly in regards to repeating signals) with some --- expense of fine control. --- --- In order to send and receive data, a modem (either wired, wireless, or ender) --- is required. The data reaches any possible destinations immediately after --- sending it, but is range limited. --- --- Rednet also allows you to use a "protocol" - simple string names indicating --- what messages are about. Receiving systems may filter messages according to --- their protocols, thereby automatically ignoring incoming messages which don't --- specify an identical string. It's also possible to @{rednet.lookup|lookup} --- which systems in the area use certain protocols, hence making it easier to --- determine where given messages should be sent in the first place. --- --- @module rednet --- @since 1.2 +--[[- The Rednet API allows computers to communicate between each other by using +@{modem|modems}. It provides a layer of abstraction on top of the main @{modem} +peripheral, making it slightly easier to use. + +## Basic usage +In order to send a message between two computers, each computer must have a +modem on one of its sides (or in the case of pocket computers and turtles, the +modem must be equipped as an upgrade). The two computers should then call +@{rednet.open}, which sets up the modems ready to send and receive messages. + +Once rednet is opened, you can send messages using @{rednet.send} and receive +them using @{rednet.receive}. It's also possible to send a message to _every_ +rednet-using computer using @{rednet.broadcast}. + +:::caution Network security + +While rednet provides a friendly way to send messages to specific computers, it +doesn't provide any guarantees about security. Other computers could be +listening in to your messages, or even pretending to send messages from other computers! + +If you're playing on a multi-player server (or at least one where you don't +trust other players), it's worth encrypting or signing your rednet messages. +::: + +## Protocols and hostnames +Several rednet messages accept "protocol"s - simple string names describing what +a message is about. When sending messages using @{rednet.send} and +@{rednet.broadcast}, you can optionally specify a protocol for the message. This +same protocol can then be given to @{rednet.receive}, to ignore all messages not +using this protocol. + +It's also possible to look-up computers based on protocols, providing a basic +system for service discovery and [DNS]. A computer can advertise that it +supports a particular protocol with @{rednet.host}, also providing a friendly +"hostname". Other computers may then find all computers which support this +protocol using @{rednet.lookup}. + +[DNS]: https://en.wikipedia.org/wiki/Domain_Name_System "Domain Name System" + +@module rednet +@since 1.2 +@see rednet_message Queued when a rednet message is received. +@see modem Rednet is built on top of the modem peripheral. Modems provide a more +bare-bones but flexible interface. +]] local expect = dofile("rom/modules/main/cc/expect.lua").expect @@ -46,9 +73,17 @@ This will open the modem on two channels: one which has the same @tparam string modem The name of the modem to open. @throws If there is no such modem with the given name -@usage Open a wireless modem on the back of the computer. +@usage Open rednet on the back of the computer, allowing you to send and receive +rednet messages using it. rednet.open("back") + +@usage Open rednet on all attached modems. This abuses the "filter" argument to +@{peripheral.find}. + + peripheral.find("modem", rednet.open) +@see rednet.close +@see rednet.isOpen ]] function open(modem) expect(1, modem, "string") @@ -65,6 +100,7 @@ end -- @tparam[opt] string modem The side the modem exists on. If not given, all -- open modems will be closed. -- @throws If there is no such modem with the given name +-- @see rednet.open function close(modem) expect(1, modem, "string", "nil") if modem then @@ -90,6 +126,7 @@ end -- modems will be checked. -- @treturn boolean If the given modem is open. -- @since 1.31 +-- @see rednet.open function isOpen(modem) expect(1, modem, "string", "nil") if modem then @@ -109,15 +146,17 @@ function isOpen(modem) end --[[- Allows a computer or turtle with an attached modem to send a message -intended for a system with a specific ID. At least one such modem must first +intended for a sycomputer with a specific ID. At least one such modem must first be @{rednet.open|opened} before sending is possible. -Assuming the target was in range and also had a correctly opened modem, it -may then use @{rednet.receive} to collect the message. +Assuming the target was in range and also had a correctly opened modem, the +target computer may then use @{rednet.receive} to collect the message. @tparam number recipient The ID of the receiving computer. -@param message The message to send. This should not contain coroutines or -functions, as they will be converted to @{nil}. +@param message The message to send. Like with @{modem.transmit}, this can +contain any primitive type (numbers, booleans and strings) as well as +tables. Other types (like functions), as well as metatables, will not be +transmitted. @tparam[opt] string protocol The "protocol" to send this message under. When using @{rednet.receive} one can filter to only receive messages sent under a particular protocol. @@ -174,16 +213,19 @@ function send(recipient, message, protocol) return sent end ---- Broadcasts a string message over the predefined @{CHANNEL_BROADCAST} --- channel. The message will be received by every device listening to rednet. --- --- @param message The message to send. This should not contain coroutines or --- functions, as they will be converted to @{nil}. --- @tparam[opt] string protocol The "protocol" to send this message under. When --- using @{rednet.receive} one can filter to only receive messages sent under a --- particular protocol. --- @see rednet.receive --- @changed 1.6 Added protocol parameter. +--[[- Broadcasts a string message over the predefined @{CHANNEL_BROADCAST} +channel. The message will be received by every device listening to rednet. + +@param message The message to send. This should not contain coroutines or +functions, as they will be converted to @{nil}. @tparam[opt] string protocol +The "protocol" to send this message under. When using @{rednet.receive} one can +filter to only receive messages sent under a particular protocol. +@see rednet.receive +@changed 1.6 Added protocol parameter. +@usage Broadcast the words "Hello, world!" to every computer using rednet. + + rednet.broadcast("Hello, world!") +]] function broadcast(message, protocol) expect(2, protocol, "string", "nil") send(CHANNEL_BROADCAST, message, protocol) @@ -263,25 +305,25 @@ function receive(protocol_filter, timeout) end end ---- Register the system as "hosting" the desired protocol under the specified --- name. If a rednet @{rednet.lookup|lookup} is performed for that protocol (and --- maybe name) on the same network, the registered system will automatically --- respond via a background process, hence providing the system performing the --- lookup with its ID number. --- --- Multiple computers may not register themselves on the same network as having --- the same names against the same protocols, and the title `localhost` is --- specifically reserved. They may, however, share names as long as their hosted --- protocols are different, or if they only join a given network after --- "registering" themselves before doing so (eg while offline or part of a --- different network). --- --- @tparam string protocol The protocol this computer provides. --- @tparam string hostname The name this protocol exposes for the given protocol. --- @throws If trying to register a hostname which is reserved, or currently in use. --- @see rednet.unhost --- @see rednet.lookup --- @since 1.6 +--[[- Register the system as "hosting" the desired protocol under the specified +name. If a rednet @{rednet.lookup|lookup} is performed for that protocol (and +maybe name) on the same network, the registered system will automatically +respond via a background process, hence providing the system performing the +lookup with its ID number. + +Multiple computers may not register themselves on the same network as having the +same names against the same protocols, and the title `localhost` is specifically +reserved. They may, however, share names as long as their hosted protocols are +different, or if they only join a given network after "registering" themselves +before doing so (eg while offline or part of a different network). + +@tparam string protocol The protocol this computer provides. +@tparam string hostname The name this protocol exposes for the given protocol. +@throws If trying to register a hostname which is reserved, or currently in use. +@see rednet.unhost +@see rednet.lookup +@since 1.6 +]] function host(protocol, hostname) expect(1, protocol, "string") expect(2, hostname, "string") @@ -306,21 +348,38 @@ function unhost(protocol) hostnames[protocol] = nil end ---- Search the local rednet network for systems @{rednet.host|hosting} the --- desired protocol and returns any computer IDs that respond as "registered" --- against it. --- --- If a hostname is specified, only one ID will be returned (assuming an exact --- match is found). --- --- @tparam string protocol The protocol to search for. --- @tparam[opt] string hostname The hostname to search for. --- --- @treturn[1] { number }|nil A list of computer IDs hosting the given --- protocol, or @{nil} if none exist. --- @treturn[2] number|nil The computer ID with the provided hostname and protocol, --- or @{nil} if none exists. --- @since 1.6 +--[[- Search the local rednet network for systems @{rednet.host|hosting} the +desired protocol and returns any computer IDs that respond as "registered" +against it. + +If a hostname is specified, only one ID will be returned (assuming an exact +match is found). + +@tparam string protocol The protocol to search for. +@tparam[opt] string hostname The hostname to search for. + +@treturn[1] number... A list of computer IDs hosting the given protocol. +@treturn[2] number|nil The computer ID with the provided hostname and protocol, +or @{nil} if none exists. +@since 1.6 +@usage Find all computers which are hosting the `"chat"` protocol. + + local computers = {rednet.lookup("chat")} + print(#computers .. " computers available to chat") + for _, computer in pairs(computers) do + print("Computer #" .. computer) + end + +@usage Find a computer hosting the `"chat"` protocol with a hostname of `"my_host"`. + + local id = rednet.lookup("chat", "my_host") + if id then + print("Found my_host at computer #" .. id) + else + printError("Cannot find my_host") + end + +]] function lookup(protocol, hostname) expect(1, protocol, "string") expect(2, hostname, "string", "nil") diff --git a/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua b/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua index aac7d1822..43343d7e5 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua @@ -108,39 +108,48 @@ local function makePagedScroll(_term, _nFreeLines) end end ---- Prints a given string to the display. --- --- If the action can be completed without scrolling, it acts much the same as --- @{print}; otherwise, it will throw up a "Press any key to continue" prompt at --- the bottom of the display. Each press will cause it to scroll down and write --- a single line more before prompting again, if need be. --- --- @tparam string _sText The text to print to the screen. --- @tparam[opt] number _nFreeLines The number of lines which will be --- automatically scrolled before the first prompt appears (meaning _nFreeLines + --- 1 lines will be printed). This can be set to the terminal's height - 2 to --- always try to fill the screen. Defaults to 0, meaning only one line is --- displayed before prompting. --- @treturn number The number of lines printed. --- @usage --- local width, height = term.getSize() --- textutils.pagedPrint(("This is a rather verbose dose of repetition.\n"):rep(30), height - 2) -function pagedPrint(_sText, _nFreeLines) - expect(2, _nFreeLines, "number", "nil") +--[[- Prints a given string to the display. + +If the action can be completed without scrolling, it acts much the same as +@{print}; otherwise, it will throw up a "Press any key to continue" prompt at +the bottom of the display. Each press will cause it to scroll down and write a +single line more before prompting again, if need be. + +@tparam string text The text to print to the screen. +@tparam[opt] number free_lines The number of lines which will be +automatically scrolled before the first prompt appears (meaning free_lines + +1 lines will be printed). This can be set to the cursor's y position - 2 to +always try to fill the screen. Defaults to 0, meaning only one line is +displayed before prompting. +@treturn number The number of lines printed. + +@usage Generates several lines of text and then prints it, paging once the +bottom of the terminal is reached. + + local lines = {} + for i = 1, 30 do lines[i] = ("This is line #%d"):format(i) end + local message = table.concat(lines, "\n") + + local width, height = term.getCursorPos() + textutils.pagedPrint(message, height - 2) +]] +function pagedPrint(text, free_lines) + expect(2, free_lines, "number", "nil") -- Setup a redirector local oldTerm = term.current() local newTerm = {} for k, v in pairs(oldTerm) do newTerm[k] = v end - newTerm.scroll = makePagedScroll(oldTerm, _nFreeLines) + + newTerm.scroll = makePagedScroll(oldTerm, free_lines) term.redirect(newTerm) -- Print the text local result local ok, err = pcall(function() - if _sText ~= nil then - result = print(_sText) + if text ~= nil then + result = print(text) else result = print() end @@ -214,32 +223,45 @@ local function tabulateCommon(bPaged, ...) end end ---- Prints tables in a structured form. --- --- This accepts multiple arguments, either a table or a number. When --- encountering a table, this will be treated as a table row, with each column --- width being auto-adjusted. --- --- When encountering a number, this sets the text color of the subsequent rows to it. --- --- @tparam {string...}|number ... The rows and text colors to display. --- @usage textutils.tabulate(colors.orange, { "1", "2", "3" }, colors.lightBlue, { "A", "B", "C" }) --- @since 1.3 +--[[- Prints tables in a structured form. + +This accepts multiple arguments, either a table or a number. When +encountering a table, this will be treated as a table row, with each column +width being auto-adjusted. + +When encountering a number, this sets the text color of the subsequent rows to it. + +@tparam {string...}|number ... The rows and text colors to display. +@since 1.3 +@usage + + textutils.tabulate( + colors.orange, { "1", "2", "3" }, + colors.lightBlue, { "A", "B", "C" } + ) +]] function tabulate(...) return tabulateCommon(false, ...) end ---- Prints tables in a structured form, stopping and prompting for input should --- the result not fit on the terminal. --- --- This functions identically to @{textutils.tabulate}, but will prompt for user --- input should the whole output not fit on the display. --- --- @tparam {string...}|number ... The rows and text colors to display. --- @usage textutils.tabulate(colors.orange, { "1", "2", "3" }, colors.lightBlue, { "A", "B", "C" }) --- @see textutils.tabulate --- @see textutils.pagedPrint --- @since 1.3 +--[[- Prints tables in a structured form, stopping and prompting for input should +the result not fit on the terminal. + +This functions identically to @{textutils.tabulate}, but will prompt for user +input should the whole output not fit on the display. + +@tparam {string...}|number ... The rows and text colors to display. +@see textutils.tabulate +@see textutils.pagedPrint +@since 1.3 + +@usage Generates a long table, tabulates it, and prints it to the screen. + + local rows = {} + for i = 1, 30 do rows[i] = {("Row #%d"):format(i), math.random(1, 400)} end + + textutils.tabulate(colors.orange, {"Column", "Value"}, colors.lightBlue, table.unpack(rows)) +]] function pagedTabulate(...) return tabulateCommon(true, ...) end @@ -692,11 +714,11 @@ saving in a file or pretty-printing. @throws If the object contains a value which cannot be serialised. This includes functions and tables which appear multiple times. -@see cc.pretty.pretty An alternative way to display a table, often more suitable for -pretty printing. +@see cc.pretty.pretty_print An alternative way to display a table, often more +suitable for pretty printing. @since 1.3 @changed 1.97.0 Added `opts` argument. -@usage Pretty print a basic table. +@usage Serialise a basic table. textutils.serialise({ 1, 2, 3, a = 1, ["another key"] = { true } }) diff --git a/src/main/resources/data/computercraft/lua/rom/apis/turtle/turtle.lua b/src/main/resources/data/computercraft/lua/rom/apis/turtle/turtle.lua index 0a66add16..6d597f375 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/turtle/turtle.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/turtle/turtle.lua @@ -1,6 +1,4 @@ ---- The turtle API allows you to control your turtle. --- --- @module turtle +--- @module turtle if not turtle then error("Cannot load turtle API on computer", 2) @@ -8,9 +6,8 @@ end --- The builtin turtle API, without any generated helper functions. -- --- Generally you should not need to use this table - it only exists for --- backwards compatibility reasons. --- @deprecated +-- @deprecated Historically this table behaved differently to the main turtle API, but this is no longer the base. You +-- should not need to use it. native = turtle.native or turtle local function addCraftMethod(object) diff --git a/src/main/resources/data/computercraft/lua/rom/help/changelog.md b/src/main/resources/data/computercraft/lua/rom/help/changelog.md index bf949acc4..899e0f255 100644 --- a/src/main/resources/data/computercraft/lua/rom/help/changelog.md +++ b/src/main/resources/data/computercraft/lua/rom/help/changelog.md @@ -1,3 +1,15 @@ +# New features in CC: Tweaked 1.100.0 + +* Speakers can now play arbitrary PCM audio. +* Add support for encoding and decoding DFPWM streams, with the `cc.audio.dfpwm` module. +* Wired modems now only render breaking progress for the part which is being broken. +* Various documentation improvements. + +Several bug fixes: +* Fix the "repeat" program not repeating broadcast rednet messages. +* Fix the drag-and-drop upload functionality writing empty files. +* Prevent turtles from pushing non-pushable entities. + # New features in CC: Tweaked 1.99.1 * Add package.searchpath to the cc.require API. (MCJack123) diff --git a/src/main/resources/data/computercraft/lua/rom/help/credits.txt b/src/main/resources/data/computercraft/lua/rom/help/credits.txt index 6625cb806..495def842 100644 --- a/src/main/resources/data/computercraft/lua/rom/help/credits.txt +++ b/src/main/resources/data/computercraft/lua/rom/help/credits.txt @@ -6,7 +6,7 @@ Uses LuaJ from http://luaj.sourceforge.net/ The ComputerCraft 1.76 update was sponsored by MinecraftU and Deep Space. Visit http://www.minecraftu.org and http://www.deepspace.me/space-cadets to find out more. -Join the ComputerCraft community online at http://www.computercraft.info +Join the ComputerCraft community online at https://computercraft.cc Follow @DanTwoHundred on Twitter! To help contribute to ComputerCraft, browse the source code at https://github.com/dan200/ComputerCraft. diff --git a/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md b/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md index ca27e718e..7ba49bd65 100644 --- a/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md +++ b/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md @@ -1,12 +1,13 @@ -New features in CC: Tweaked 1.99.1 +New features in CC: Tweaked 1.100.0 -* Add package.searchpath to the cc.require API. (MCJack123) -* Provide a more efficient way for the Java API to consume Lua tables in certain restricted cases. +* Speakers can now play arbitrary PCM audio. +* Add support for encoding and decoding DFPWM streams, with the `cc.audio.dfpwm` module. +* Wired modems now only render breaking progress for the part which is being broken. +* Various documentation improvements. Several bug fixes: -* Fix keys being "sticky" when opening the off-hand pocket computer GUI. -* Correctly handle broken coroutine managers resuming Java code with a `nil` event. -* Prevent computer buttons stealing focus from the terminal. -* Fix a class cast exception when a monitor is malformed in ways I do not quite understand. +* Fix the "repeat" program not repeating broadcast rednet messages. +* Fix the drag-and-drop upload functionality writing empty files. +* Prevent turtles from pushing non-pushable entities. Type "help changelog" to see the full version history. diff --git a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/audio/dfpwm.lua b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/audio/dfpwm.lua index f3824f9cb..6f8e8a99f 100644 --- a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/audio/dfpwm.lua +++ b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/audio/dfpwm.lua @@ -36,7 +36,7 @@ local encoder = dfpwm.make_encoder() local decoder = dfpwm.make_decoder() local out = fs.open("speedy.dfpwm", "wb") -for input in io.lines("my_audio_track.dfpwm", 16 * 1024 * 2) do +for input in io.lines("data/example.dfpwm", 16 * 1024 * 2) do local decoded = decoder(input) local output = {} diff --git a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/pretty.lua b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/pretty.lua index 7a918df59..b4337381d 100644 --- a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/pretty.lua +++ b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/pretty.lua @@ -224,8 +224,8 @@ end --- Display a document on the terminal. -- --- @tparam Doc doc The document to render --- @tparam[opt] number ribbon_frac The maximum fraction of the width that we should write in. +-- @tparam Doc doc The document to render +-- @tparam[opt=0.6] number ribbon_frac The maximum fraction of the width that we should write in. local function write(doc, ribbon_frac) if getmetatable(doc) ~= Doc then expect(1, doc, "document") end expect(2, ribbon_frac, "number", "nil") @@ -286,8 +286,8 @@ end --- Display a document on the terminal with a trailing new line. -- --- @tparam Doc doc The document to render. --- @tparam[opt] number ribbon_frac The maximum fraction of the width that we should write in. +-- @tparam Doc doc The document to render. +-- @tparam[opt=0.6] number ribbon_frac The maximum fraction of the width that we should write in. local function print(doc, ribbon_frac) if getmetatable(doc) ~= Doc then expect(1, doc, "document") end expect(2, ribbon_frac, "number", "nil") @@ -297,10 +297,10 @@ end --- Render a document, converting it into a string. -- --- @tparam Doc doc The document to render. --- @tparam[opt] number width The maximum width of this document. Note that long strings will not be wrapped to --- fit this width - it is only used for finding the best layout. --- @tparam[opt] number ribbon_frac The maximum fraction of the width that we should write in. +-- @tparam Doc doc The document to render. +-- @tparam[opt] number width The maximum width of this document. Note that long strings will not be wrapped to fit +-- this width - it is only used for finding the best layout. +-- @tparam[opt=0.6] number ribbon_frac The maximum fraction of the width that we should write in. -- @treturn string The rendered document as a string. local function render(doc, width, ribbon_frac) if getmetatable(doc) ~= Doc then expect(1, doc, "document") end @@ -483,7 +483,7 @@ Controls how various properties are displayed. - `function_args`: Show the arguments to a function if known (`false` by default). - `function_source`: Show where the function was defined, instead of `function: xxxxxxxx` (`false` by default). -@tparam[opt] number ribbon_frac The maximum fraction of the width that we should write in. +@tparam[opt=0.6] number ribbon_frac The maximum fraction of the width that we should write in. @usage Display a table on the screen. diff --git a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/require.lua b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/require.lua index ecf17fe8e..e0fa83aa9 100644 --- a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/require.lua +++ b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/require.lua @@ -1,22 +1,24 @@ ---- This provides a pure Lua implementation of the builtin @{require} function --- and @{package} library. --- --- Generally you do not need to use this module - it is injected into the --- every program's environment. However, it may be useful when building a --- custom shell or when running programs yourself. --- --- @module cc.require --- @since 1.88.0 --- @usage Construct the package and require function, and insert them into a --- custom environment. --- --- local r = require "cc.require" --- local env = setmetatable({}, { __index = _ENV }) --- env.require, env.package = r.make(env, "/") --- --- -- Now we have our own require function, separate to the original. --- local r2 = env.require "cc.require" --- print(r, r2) +--[[- This provides a pure Lua implementation of the builtin @{require} function +and @{package} library. + +Generally you do not need to use this module - it is injected into the every +program's environment. However, it may be useful when building a custom shell or +when running programs yourself. + +@module cc.require +@since 1.88.0 +@see using_require For an introduction on how to use @{require}. +@usage Construct the package and require function, and insert them into a +custom environment. + + local r = require "cc.require" + local env = setmetatable({}, { __index = _ENV }) + env.require, env.package = r.make(env, "/") + + -- Now we have our own require function, separate to the original. + local r2 = env.require "cc.require" + print(r, r2) +]] local expect = require and require("cc.expect") or dofile("rom/modules/main/cc/expect.lua") local expect = expect.expect diff --git a/src/main/resources/data/computercraft/lua/rom/programs/help.lua b/src/main/resources/data/computercraft/lua/rom/programs/help.lua index 9c7081ecc..22c70f83e 100644 --- a/src/main/resources/data/computercraft/lua/rom/programs/help.lua +++ b/src/main/resources/data/computercraft/lua/rom/programs/help.lua @@ -171,7 +171,7 @@ end local current_section = nil local offset = 0 ---- Find the currently visible seciton, or nil if this document has no sections. +--- Find the currently visible section, or nil if this document has no sections. -- -- This could potentially be a binary search, but right now it's not worth it. local function find_section() diff --git a/src/web/index.tsx b/src/web/index.tsx index d795b3b2d..c8e0f8dfd 100644 --- a/src/web/index.tsx +++ b/src/web/index.tsx @@ -26,13 +26,14 @@ const Click = (options: { run: () => void }) => type WindowProps = {}; -type WindowState = { - visible: boolean, - - example: string, - exampleIdx: number, +type Example = { + files: { [file: string]: string }, } +type WindowState = { + exampleIdx: number, +} & ({ visible: false, example: null } | { visible: true, example: Example }) + type Touch = { clientX: number, clientY: number }; class Window extends Component { @@ -41,12 +42,14 @@ class Window extends Component { private top: number = 0; private dragging?: { downX: number, downY: number, initialX: number, initialY: number }; + private snippets: { [file: string]: string } = {}; + constructor(props: WindowProps, context: unknown) { super(props, context); this.state = { visible: false, - example: "", + example: null, exampleIdx: 0, } } @@ -57,10 +60,16 @@ class Window extends Component { const element = elements[i] as HTMLElement; let example = element.innerText; + + const snippet = element.getAttribute("data-snippet"); + if (snippet) this.snippets[snippet] = example; + if (element.getAttribute("data-lua-kind") == "expr") { example = exprTemplate.replace("__expr__", example); } - render(, element); + + const mount = element.getAttribute("data-mount"); + render(, element); } } @@ -76,13 +85,13 @@ class Window extends Component {
:
; } - private runExample(example: string): () => void { + private runExample(example: string, mount: string | null): () => void { return () => { if (!this.positioned) { this.positioned = true; @@ -90,9 +99,17 @@ class Window extends Component { this.top = 20; } + const files: { [file: string]: string } = { "example.lua": example }; + if (mount !== null) { + for (const toMount of mount.split(",")) { + const [name, path] = toMount.split(":", 2); + files[path] = this.snippets[name] || ""; + } + } + this.setState(({ exampleIdx }: WindowState) => ({ visible: true, - example: example, + example: { files }, exampleIdx: exampleIdx + 1, })); }