mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-11-02 14:43:00 +00:00
Compare commits
55 Commits
v1.15.2-1.
...
v1.15.2-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87aa839b60 | ||
|
|
e02ccdcb1a | ||
|
|
f36f532c63 | ||
|
|
5a816917d5 | ||
|
|
7af63d052d | ||
|
|
4f8217d1ab | ||
|
|
5409d441b5 | ||
|
|
d5f82fa458 | ||
|
|
d0deab3519 | ||
|
|
d5a8df753a | ||
|
|
13de2c4dd0 | ||
|
|
906280225e | ||
|
|
161a5b4707 | ||
|
|
c6b6b4479c | ||
|
|
96e7b60285 | ||
|
|
086fccd997 | ||
|
|
5dfaf6eee9 | ||
|
|
e251dd066c | ||
|
|
9abcfe56ea | ||
|
|
abbc641fd4 | ||
|
|
c60dcb4f5a | ||
|
|
4be0b15afa | ||
|
|
a4ae36b6b3 | ||
|
|
ac075d9f53 | ||
|
|
05d7be0362 | ||
|
|
9a71dc1a26 | ||
|
|
156023b154 | ||
|
|
6b3773a862 | ||
|
|
376d628cf0 | ||
|
|
44062ebd52 | ||
|
|
5739285fc2 | ||
|
|
70b457ed18 | ||
|
|
ca2995ed38 | ||
|
|
6816931659 | ||
|
|
1547ecbeb3 | ||
|
|
e918f55b58 | ||
|
|
c28b468844 | ||
|
|
052cf8ee7d | ||
|
|
550ada2f9e | ||
|
|
17b7727262 | ||
|
|
4553e404b2 | ||
|
|
a565a571f9 | ||
|
|
fb64b6017b | ||
|
|
ed4229ab70 | ||
|
|
3fb906ef6c | ||
|
|
e1663f3df0 | ||
|
|
52c6584c81 | ||
|
|
9f87eda5de | ||
|
|
697e9449cf | ||
|
|
76c3e4c155 | ||
|
|
358289b5f9 | ||
|
|
5eec24676f | ||
|
|
f52b8fa2de | ||
|
|
447c3ab125 | ||
|
|
8fac68386e |
3
.github/workflows/main-ci.yml
vendored
3
.github/workflows/main-ci.yml
vendored
@@ -32,6 +32,9 @@ jobs:
|
||||
name: CC-Tweaked
|
||||
path: build/libs
|
||||
|
||||
- name: Upload Coverage
|
||||
run: bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
lint-lua:
|
||||
name: Lint Lua
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
13
build.gradle
13
build.gradle
@@ -17,6 +17,7 @@ buildscript {
|
||||
|
||||
plugins {
|
||||
id "checkstyle"
|
||||
id "jacoco"
|
||||
id "com.github.hierynomus.license" version "0.15.0"
|
||||
id "com.matthewprenger.cursegradle" version "1.3.0"
|
||||
id "com.github.breadmoirai.github-release" version "2.2.4"
|
||||
@@ -108,6 +109,7 @@ dependencies {
|
||||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.2'
|
||||
testImplementation 'org.hamcrest:hamcrest:2.2'
|
||||
|
||||
deployerJars "org.apache.maven.wagon:wagon-ssh:3.0.0"
|
||||
}
|
||||
@@ -288,6 +290,15 @@ test {
|
||||
}
|
||||
}
|
||||
|
||||
jacocoTestReport {
|
||||
reports {
|
||||
xml.enabled true
|
||||
html.enabled true
|
||||
}
|
||||
}
|
||||
|
||||
check.dependsOn jacocoTestReport
|
||||
|
||||
license {
|
||||
mapping("java", "SLASHSTAR_STYLE")
|
||||
strictCheck true
|
||||
@@ -373,7 +384,7 @@ curseforge {
|
||||
apiKey = project.hasProperty('curseForgeApiKey') ? project.curseForgeApiKey : ''
|
||||
project {
|
||||
id = '282001'
|
||||
releaseType = 'alpha'
|
||||
releaseType = 'beta'
|
||||
changelog = "Release notes can be found on the GitHub repository (https://github.com/SquidDev-CC/CC-Tweaked/releases/tag/v${mc_version}-${mod_version})."
|
||||
|
||||
relations {
|
||||
|
||||
@@ -114,11 +114,11 @@
|
||||
</module>
|
||||
<module name="ParameterName" />
|
||||
<module name="StaticVariableName">
|
||||
<property name="format" value="^[a-z][a-zA-Z0-9]*|CAPABILITY(_[A-Z]+)?$" />
|
||||
<property name="format" value="^[a-z][a-zA-Z0-9]*|CAPABILITY(_[A-Z_]+)?$" />
|
||||
<property name="applyToPrivate" value="false" />
|
||||
</module>
|
||||
<module name="StaticVariableName">
|
||||
<property name="format" value="^(s_)?[a-z][a-zA-Z0-9]*|CAPABILITY(_[A-Z]+)?$" />
|
||||
<property name="format" value="^(s_)?[a-z][a-zA-Z0-9]*|CAPABILITY(_[A-Z_]+)?$" />
|
||||
<property name="applyToPrivate" value="true" />
|
||||
</module>
|
||||
<module name="TypeName" />
|
||||
|
||||
@@ -1,6 +1,77 @@
|
||||
--- Execute a specific command.
|
||||
--
|
||||
-- @tparam string command The command to execute.
|
||||
-- @treturn boolean Whether the command executed successfully.
|
||||
-- @treturn { string... } The output of this command, as a list of lines.
|
||||
-- @treturn number|nil The number of "affected" objects, or `nil` if the command
|
||||
-- failed. The definition of this varies from command to command.
|
||||
-- @usage Set the block above the command computer to stone.
|
||||
--
|
||||
-- commands.exec("setblock ~ ~1 ~ minecraft:stone")
|
||||
function exec(command) end
|
||||
|
||||
--- Asynchronously execute a command.
|
||||
--
|
||||
-- Unlike @{exec}, this will immediately return, instead of waiting for the
|
||||
-- command to execute. This allows you to run multiple commands at the same
|
||||
-- time.
|
||||
--
|
||||
-- When this command has finished executing, it will queue a `task_complete`
|
||||
-- event containing the result of executing this command (what @{exec} would
|
||||
-- return).
|
||||
--
|
||||
-- @tparam string command The command to execute.
|
||||
-- @treturn number The "task id". When this command has been executed, it will
|
||||
-- queue a `task_complete` event with a matching id.
|
||||
-- @usage Asynchronously sets the block above the computer to stone.
|
||||
--
|
||||
-- commands.execAsync("~ ~1 ~ minecraft:stone")
|
||||
-- @see parallel One may also use the parallel API to run multiple commands at
|
||||
-- once.
|
||||
function execAsync(commad) end
|
||||
|
||||
--- List all available commands which the computer has permission to execute.
|
||||
--
|
||||
-- @treturn { string... } A list of all available commands
|
||||
function list() end
|
||||
|
||||
--- Get the position of the current command computer.
|
||||
--
|
||||
-- @treturn number This computer's x position.
|
||||
-- @treturn number This computer's y position.
|
||||
-- @treturn number This computer's z position.
|
||||
-- @see gps.locate To get the position of a non-command computer.
|
||||
function getBlockPosition() end
|
||||
function getBlockInfos(min_x, min_y, min_z, max_x, max_y, max_z) end
|
||||
|
||||
--- Get some basic information about a block.
|
||||
--
|
||||
-- The returned table contains the current name, metadata and block state (as
|
||||
-- with @{turtle.inspect}). If there is a tile entity for that block, its NBT
|
||||
-- will also be returned.
|
||||
--
|
||||
-- @tparam number x The x position of the block to query.
|
||||
-- @tparam number y The y position of the block to query.
|
||||
-- @tparam number z The z position of the block to query.
|
||||
-- @treturn table The given block's information.
|
||||
-- @throws If the coordinates are not within the world, or are not currently
|
||||
-- loaded.
|
||||
function getBlockInfo(x, y, z) end
|
||||
|
||||
--- Get information about a range of blocks.
|
||||
--
|
||||
-- This returns the same information as @{getBlockInfo}, just for multiple
|
||||
-- blocks at once.
|
||||
--
|
||||
-- Blocks are traversed by ascending y level, followed by z and x - the returned
|
||||
-- table may be indexed using `x + z*width + y*depth*depth`.
|
||||
--
|
||||
-- @tparam number min_x The start x coordinate of the range to query.
|
||||
-- @tparam number min_y The start y coordinate of the range to query.
|
||||
-- @tparam number min_z The start z coordinate of the range to query.
|
||||
-- @tparam number max_x The end x coordinate of the range to query.
|
||||
-- @tparam number max_y The end y coordinate of the range to query.
|
||||
-- @tparam number max_z The end z coordinate of the range to query.
|
||||
-- @treturn { table... } A list of information about each block.
|
||||
-- @throws If the coordinates are not within the world.
|
||||
-- @throws If trying to get information about more than 4096 blocks.
|
||||
function getBlockInfos(min_x, min_y, min_z, max_x, max_y, max_z) end
|
||||
|
||||
@@ -19,6 +19,48 @@ function getFreeSpace(path) end
|
||||
function find(pattern) end
|
||||
function getDir(path) end
|
||||
|
||||
--- Returns true if a path is mounted to the parent filesystem.
|
||||
--
|
||||
-- The root filesystem "/" is considered a mount, along with disk folders and
|
||||
-- the rom folder. Other programs (such as network shares) can exstend this to
|
||||
-- make other mount types by correctly assigning their return value for getDrive.
|
||||
--
|
||||
-- @tparam string path The path to check.
|
||||
-- @treturn boolean If the path is mounted, rather than a normal file/folder.
|
||||
-- @throws If the path does not exist.
|
||||
-- @see getDrive
|
||||
function isDriveRoot(path) end
|
||||
|
||||
--- Get the capacity of the drive at the given path.
|
||||
--
|
||||
-- This may be used in conjunction with @{getFreeSpace} to determine what
|
||||
-- percentage of this drive has been used.
|
||||
--
|
||||
-- @tparam string path The path of the drive to get.
|
||||
-- @treturn number This drive's capacity. This will be 0 for "read-only" drives,
|
||||
-- such as the ROM or treasure disks.
|
||||
function getCapacity(path) end
|
||||
|
||||
--- Get attributes about a specific file or folder.
|
||||
--
|
||||
-- The returned attributes table contains information about the size of the
|
||||
-- file, whether it is a directory, and when it was created and last modified.
|
||||
--
|
||||
-- The creation and modification times are given as the number of milliseconds
|
||||
-- since the UNIX epoch. This may be given to @{os.date} in order to convert it
|
||||
-- to more usable form.
|
||||
--
|
||||
-- @tparam string path The path to get attributes for.
|
||||
-- @treturn { size = number, isDir = boolean, created = number, modified = number }
|
||||
-- The resulting attributes.
|
||||
-- @throws If the path does not exist.
|
||||
-- @see getSize If you only care about the file's size.
|
||||
-- @see isDir If you only care whether a path is a directory or not.
|
||||
function attributes(path) end
|
||||
|
||||
-- Defined in bios.lua
|
||||
function complete(sPath, sLocation, bIncludeFiles, bIncludeDirs) end
|
||||
|
||||
--- A file handle which can be read from.
|
||||
--
|
||||
-- @type ReadHandle
|
||||
|
||||
@@ -15,3 +15,10 @@ function cancelTimer(id) end
|
||||
function cancelAlarm(id) end
|
||||
function epoch(timezone) end
|
||||
function date(format, time) end
|
||||
|
||||
-- Defined in bios.lua
|
||||
function loadAPI(path) end
|
||||
function pullEvent(filter) end
|
||||
function pullEventRaw(filter) end
|
||||
function version() end
|
||||
function run(env, path, ...) end
|
||||
|
||||
28
doc/stub/pocket.lua
Normal file
28
doc/stub/pocket.lua
Normal file
@@ -0,0 +1,28 @@
|
||||
--[[-
|
||||
Control the current pocket computer, adding or removing upgrades.
|
||||
|
||||
This API is only available on pocket computers. As such, you may use its
|
||||
presence to determine what kind of computer you are using:
|
||||
|
||||
```lua
|
||||
if pocket then
|
||||
print("On a pocket computer")
|
||||
else
|
||||
print("On something else")
|
||||
end
|
||||
```
|
||||
]]
|
||||
|
||||
--- Search the player's inventory for another upgrade, replacing the existing
|
||||
-- one with that item if found.
|
||||
--
|
||||
-- This inventory search starts from the player's currently selected slot,
|
||||
-- allowing you to prioritise upgrades.
|
||||
--
|
||||
-- @throws If an upgrade cannot be found.
|
||||
function equipBack() end
|
||||
|
||||
--- Remove the pocket computer's current upgrade.
|
||||
--
|
||||
-- @throws If this pocket computer does not currently have an upgrade.
|
||||
function unequipBack() end
|
||||
@@ -1,14 +1,120 @@
|
||||
--[[- Interact with redstone attached to this computer.
|
||||
|
||||
The @{redstone} library exposes three "types" of redstone control:
|
||||
- Binary input/output (@{setOutput}/@{getInput}): These simply check if a
|
||||
redstone wire has any input or output. A signal strength of 1 and 15 are
|
||||
treated the same.
|
||||
- Analogue input/output (@{setAnalogueOutput}/@{getAnalogueInput}): These
|
||||
work with the actual signal strength of the redstone wired, from 0 to 15.
|
||||
- Bundled cables (@{setBundledOutput}/@{getBundledInput}): These interact with
|
||||
"bundled" cables, such 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 `redstone` event will be fired. This may
|
||||
be used in or
|
||||
|
||||
This module may also be referred to as `rs`. For example, one may call
|
||||
`rs.getSides()` instead of @{redstone.getSides}.
|
||||
|
||||
@module redstone
|
||||
@usage Toggle the redstone signal above the computer every 0.5 seconds.
|
||||
|
||||
while true do
|
||||
redstone.setOutput("top", not redstone.getOutput("top"))
|
||||
sleep(0.5)
|
||||
end
|
||||
@usage Mimic a redstone comparator in [subtraction mode][comparator].
|
||||
|
||||
while true do
|
||||
local rear = rs.getAnalogueInput("back")
|
||||
local sides = math.max(rs.getAnalogueInput("left"), rs.getAnalogueInput("right"))
|
||||
rs.setAnalogueOutput("front", math.max(rear - sides, 0))
|
||||
|
||||
os.pullEvent("redstone") -- Wait for a change to inputs.
|
||||
end
|
||||
|
||||
[comparator]: https://minecraft.gamepedia.com/Redstone_Comparator#Subtract_signal_strength "Redstone Comparator on the Minecraft wiki."
|
||||
]]
|
||||
|
||||
--- Returns a table containing the six sides of the computer. Namely, "top",
|
||||
-- "bottom", "left", "right", "front" and "back".
|
||||
--
|
||||
-- @treturn { string... } A table of valid sides.
|
||||
function getSides() end
|
||||
|
||||
--- Turn the redstone signal of a specific side on or off.
|
||||
--
|
||||
-- @tparam string side The side to set.
|
||||
-- @tparam boolean on Whether the redstone signal should be on or off. When on,
|
||||
-- a signal strength of 15 is emitted.
|
||||
function setOutput(side, on) end
|
||||
|
||||
--- Get the current redstone output of a specific side.
|
||||
--
|
||||
-- @tparam string side The side to get.
|
||||
-- @treturn boolean Whether the redstone output is on or off.
|
||||
-- @see setOutput
|
||||
function getOutput(side) end
|
||||
|
||||
--- Get the current redstone input of a specific side.
|
||||
--
|
||||
-- @tparam string side The side to get.
|
||||
-- @treturn boolean Whether the redstone input is on or off.
|
||||
function getInput(side) end
|
||||
function setBundledOutput(side, output) end
|
||||
function getBundledOutput(side) end
|
||||
function getBundledInput(side) end
|
||||
function testBundledInput(side, mask) end
|
||||
|
||||
--- Set the redstone signal strength for a specific side.
|
||||
--
|
||||
-- @tparam string side The side to set.
|
||||
-- @tparam number value The signal strength, between 0 and 15.
|
||||
-- @throws If `value` is not between 0 and 15.
|
||||
function setAnalogOutput(side, value) end
|
||||
setAnalogueOutput = setAnalogOutput
|
||||
|
||||
--- Get the redstone output signal strength for a specific side.
|
||||
--
|
||||
-- @tparam string side The side to get.
|
||||
-- @treturn number The output signal strength, between 0 and 15.
|
||||
-- @see setAnalogueOutput
|
||||
function getAnalogOutput(sid) end
|
||||
getAnalogueOutput = getAnalogOutput
|
||||
|
||||
--- Get the redstone input signal strength for a specific side.
|
||||
--
|
||||
-- @tparam string side The side to get.
|
||||
-- @treturn number The input signal strength, between 0 and 15.
|
||||
function getAnalogInput(side) end
|
||||
getAnalogueInput = getAnaloguInput
|
||||
getAnalogueInput = getAnalogInput
|
||||
|
||||
--- Set the bundled cable output for a specific side.
|
||||
--
|
||||
-- @tparam string side The side to set.
|
||||
-- @tparam number The colour bitmask to set.
|
||||
-- @see colors.subtract For removing a colour from the bitmask.
|
||||
-- @see colors.combine For adding a colour to the bitmask.
|
||||
function setBundledOutput(side, output) end
|
||||
|
||||
--- Get the bundled cable output for a specific side.
|
||||
--
|
||||
-- @tparam string side The side to get.
|
||||
-- @treturn number The bundled cable's output.
|
||||
function getBundledOutput(side) end
|
||||
|
||||
--- Get the bundled cable input for a specific side.
|
||||
--
|
||||
-- @tparam string side The side to get.
|
||||
-- @treturn number The bundled cable's input.
|
||||
-- @see testBundledInput To determine if a specific colour is set.
|
||||
function getBundledInput(side) end
|
||||
|
||||
--- Determine if a specific combination of colours are on for the given side.
|
||||
--
|
||||
-- @tparam string side The side to test.
|
||||
-- @tparam number mask The mask to test.
|
||||
-- @see getBundledInput
|
||||
-- @see colors.combine For adding a colour to the bitmask.
|
||||
-- @usage Check if @{colors.white} and @{colors.black} are on for above the
|
||||
-- computer.
|
||||
--
|
||||
-- print(redstone.testBundledInput("top", colors.combine(colors.white, colors.black)))
|
||||
function testBundledInput(side, mask) end
|
||||
|
||||
@@ -15,14 +15,14 @@ isColor = isColour
|
||||
function getTextColour() end
|
||||
getTextColor = getTextColor
|
||||
function getBackgroundColour() end
|
||||
getBackgroundColour = getBackgroundColour
|
||||
getBackgroundColor = getBackgroundColour
|
||||
function blit(text, text_colours, background_colours) end
|
||||
function setPaletteColour(colour, ...) end
|
||||
setPaletteColour = setPaletteColour
|
||||
setPaletteColor = setPaletteColour
|
||||
function getPaletteColour(colour, ...) end
|
||||
getPaletteColour = getPaletteColour
|
||||
getPaletteColor = getPaletteColour
|
||||
function nativePaletteColour(colour) end
|
||||
nativePaletteColour = nativePaletteColour
|
||||
nativePaletteColor = nativePaletteColour
|
||||
|
||||
--- @type Redirect
|
||||
local Redirect = {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Mod properties
|
||||
mod_version=1.87.1
|
||||
mod_version=1.88.0
|
||||
|
||||
# Minecraft properties (update mods.toml when changing)
|
||||
mc_version=1.15.2
|
||||
forge_version=31.1.41
|
||||
mappings_version=20200410-1.15.1
|
||||
mappings_version=20200429-1.15.1
|
||||
|
||||
@@ -33,17 +33,27 @@
|
||||
|
||||
;; It's useful to name arguments for documentation, so we allow this. It'd
|
||||
;; be good to find a compromise in the future, but this works for now.
|
||||
-var:unused-arg
|
||||
-var:unused-arg)
|
||||
|
||||
;; Some APIS (keys, colour and os mainly) are incomplete right now.
|
||||
-var:unresolved-member)
|
||||
(lint
|
||||
(bracket-spaces
|
||||
(call no-space)
|
||||
(function-args no-space)
|
||||
(parens no-space)
|
||||
(table space)
|
||||
(index no-space))))
|
||||
(index no-space))
|
||||
|
||||
;; colours imports from colors, and we don't handle that right now.
|
||||
;; keys is entirely dynamic, so we skip it.
|
||||
(dynamic-modules colours keys)
|
||||
|
||||
(globals
|
||||
:max
|
||||
_CC_DEFAULT_SETTINGS
|
||||
_CC_DISABLE_LUA51_FEATURES
|
||||
;; Ideally we'd pick these up from bios.lua, but illuaminate currently
|
||||
;; isn't smart enough.
|
||||
sleep write printError read rs)))
|
||||
|
||||
;; We disable the unused global linter in bios.lua and the APIs. In the future
|
||||
;; hopefully we'll get illuaminate to handle this.
|
||||
@@ -60,17 +70,13 @@
|
||||
|
||||
;; Suppress warnings for currently undocumented modules.
|
||||
(at
|
||||
(/doc/stub/commands.lua
|
||||
/doc/stub/fs.lua
|
||||
(/doc/stub/fs.lua
|
||||
/doc/stub/http.lua
|
||||
/doc/stub/os.lua
|
||||
/doc/stub/redstone.lua
|
||||
/doc/stub/term.lua
|
||||
/doc/stub/turtle.lua
|
||||
/src/main/resources/*/computercraft/lua/rom/apis/command/commands.lua
|
||||
/src/main/resources/*/computercraft/lua/rom/apis/io.lua
|
||||
/src/main/resources/*/computercraft/lua/rom/apis/window.lua
|
||||
/src/main/resources/*/computercraft/lua/rom/modules/main/cc/shell/completion.lua)
|
||||
/src/main/resources/*/computercraft/lua/rom/apis/window.lua)
|
||||
|
||||
(linters -doc:undocumented -doc:undocumented-arg))
|
||||
|
||||
@@ -79,6 +85,11 @@
|
||||
(/src/main/resources/*/computercraft/lua/rom/apis/textutils.lua
|
||||
/src/main/resources/*/computercraft/lua/rom/modules/main/cc/completion.lua
|
||||
/src/main/resources/*/computercraft/lua/rom/modules/main/cc/shell/completion.lua
|
||||
/src/main/resources/*/computercraft/lua/rom/programs/advanced/multishell.lua
|
||||
/src/main/resources/*/computercraft/lua/rom/programs/shell.lua)
|
||||
(linters -doc:unresolved-reference))
|
||||
|
||||
(at /src/test/resources/test-rom
|
||||
(lint
|
||||
(globals
|
||||
:max sleep write
|
||||
cct_test describe expect howlci fail it pending stub)))
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
{
|
||||
"condition": "computercraft:block_named"
|
||||
},
|
||||
{
|
||||
"condition": "computercraft:has_id"
|
||||
},
|
||||
{
|
||||
"condition": "minecraft:inverted",
|
||||
"term": {
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
{
|
||||
"condition": "computercraft:block_named"
|
||||
},
|
||||
{
|
||||
"condition": "computercraft:has_id"
|
||||
},
|
||||
{
|
||||
"condition": "minecraft:inverted",
|
||||
"term": {
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
{
|
||||
"condition": "computercraft:block_named"
|
||||
},
|
||||
{
|
||||
"condition": "computercraft:has_id"
|
||||
},
|
||||
{
|
||||
"condition": "minecraft:inverted",
|
||||
"term": {
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
{
|
||||
"condition": "computercraft:block_named"
|
||||
},
|
||||
{
|
||||
"condition": "computercraft:has_id"
|
||||
},
|
||||
{
|
||||
"condition": "minecraft:inverted",
|
||||
"term": {
|
||||
|
||||
1
src/generated/resources/data/computercraft/loot_tables/treasure_disk.json
generated
Normal file
1
src/generated/resources/data/computercraft/loot_tables/treasure_disk.json
generated
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -6,7 +6,8 @@
|
||||
package dan200.computercraft;
|
||||
|
||||
import dan200.computercraft.api.turtle.event.TurtleAction;
|
||||
import dan200.computercraft.core.apis.http.AddressRule;
|
||||
import dan200.computercraft.core.apis.http.options.Action;
|
||||
import dan200.computercraft.core.apis.http.options.AddressRule;
|
||||
import dan200.computercraft.shared.Config;
|
||||
import dan200.computercraft.shared.computer.blocks.BlockComputer;
|
||||
import dan200.computercraft.shared.computer.core.ClientComputerRegistry;
|
||||
@@ -67,34 +68,35 @@ public final class ComputerCraft
|
||||
public static int computerSpaceLimit = 1000 * 1000;
|
||||
public static int floppySpaceLimit = 125 * 1000;
|
||||
public static int maximumFilesOpen = 128;
|
||||
public static boolean disable_lua51_features = false;
|
||||
public static String default_computer_settings = "";
|
||||
public static boolean debug_enable = true;
|
||||
public static boolean logPeripheralErrors = false;
|
||||
public static boolean disableLua51Features = false;
|
||||
public static String defaultComputerSettings = "";
|
||||
public static boolean debugEnable = true;
|
||||
public static boolean logComputerErrors = true;
|
||||
public static boolean commandRequireCreative = true;
|
||||
|
||||
public static int computer_threads = 1;
|
||||
public static int computerThreads = 1;
|
||||
public static long maxMainGlobalTime = TimeUnit.MILLISECONDS.toNanos( 10 );
|
||||
public static long maxMainComputerTime = TimeUnit.MILLISECONDS.toNanos( 5 );
|
||||
|
||||
public static boolean httpEnabled = true;
|
||||
public static boolean httpWebsocketEnabled = true;
|
||||
public static List<AddressRule> httpRules = Collections.unmodifiableList( Stream.concat(
|
||||
Stream.of( DEFAULT_HTTP_DENY ).map( x -> AddressRule.parse( x, AddressRule.Action.DENY ) ).filter( Objects::nonNull ),
|
||||
Stream.of( DEFAULT_HTTP_ALLOW ).map( x -> AddressRule.parse( x, AddressRule.Action.ALLOW ) ).filter( Objects::nonNull )
|
||||
Stream.of( DEFAULT_HTTP_DENY )
|
||||
.map( x -> AddressRule.parse( x, Action.DENY.toPartial() ) )
|
||||
.filter( Objects::nonNull ),
|
||||
Stream.of( DEFAULT_HTTP_ALLOW )
|
||||
.map( x -> AddressRule.parse( x, Action.ALLOW.toPartial() ) )
|
||||
.filter( Objects::nonNull )
|
||||
).collect( Collectors.toList() ) );
|
||||
|
||||
public static int httpTimeout = 30000;
|
||||
public static int httpMaxRequests = 16;
|
||||
public static long httpMaxDownload = 16 * 1024 * 1024;
|
||||
public static long httpMaxUpload = 4 * 1024 * 1024;
|
||||
public static int httpMaxWebsockets = 4;
|
||||
public static int httpMaxWebsocketMessage = 128 * 1024;
|
||||
|
||||
public static boolean enableCommandBlock = false;
|
||||
public static int modem_range = 64;
|
||||
public static int modem_highAltitudeRange = 384;
|
||||
public static int modem_rangeDuringStorm = 64;
|
||||
public static int modem_highAltitudeRangeDuringStorm = 384;
|
||||
public static int modemRange = 64;
|
||||
public static int modemHighAltitudeRange = 384;
|
||||
public static int modemRangeDuringStorm = 64;
|
||||
public static int modemHighAltitudeRangeDuringStorm = 384;
|
||||
public static int maxNotesPerTick = 8;
|
||||
public static MonitorRenderer monitorRenderer = MonitorRenderer.BEST;
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import dan200.computercraft.core.filesystem.ResourceMount;
|
||||
import dan200.computercraft.shared.*;
|
||||
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessNetwork;
|
||||
import dan200.computercraft.shared.util.IDAssigner;
|
||||
import dan200.computercraft.shared.wired.CapabilityWiredElement;
|
||||
import dan200.computercraft.shared.wired.WiredNode;
|
||||
import net.minecraft.resources.IReloadableResourceManager;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
@@ -41,6 +40,8 @@ import java.io.File;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Map;
|
||||
|
||||
import static dan200.computercraft.shared.Capabilities.CAPABILITY_WIRED_ELEMENT;
|
||||
|
||||
public final class ComputerCraftAPIImpl implements IComputerCraftAPI
|
||||
{
|
||||
public static final ComputerCraftAPIImpl INSTANCE = new ComputerCraftAPIImpl();
|
||||
@@ -147,6 +148,6 @@ public final class ComputerCraftAPIImpl implements IComputerCraftAPI
|
||||
public LazyOptional<IWiredElement> getWiredElementAt( @Nonnull IBlockReader world, @Nonnull BlockPos pos, @Nonnull Direction side )
|
||||
{
|
||||
TileEntity tile = world.getTileEntity( pos );
|
||||
return tile == null ? LazyOptional.empty() : tile.getCapability( CapabilityWiredElement.CAPABILITY, side );
|
||||
return tile == null ? LazyOptional.empty() : tile.getCapability( CAPABILITY_WIRED_ELEMENT, side );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,334 +0,0 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
|
||||
* For help using the API, and posting your mods, visit the forums at computercraft.info.
|
||||
*/
|
||||
package dan200.computercraft.api.lua;
|
||||
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Provides methods for extracting values and validating Lua arguments, such as those provided to
|
||||
* {@link ILuaObject#callMethod(ILuaContext, int, Object[])} or
|
||||
* {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}.
|
||||
*
|
||||
* This provides two sets of functions: the {@code get*} methods, which require an argument to be valid, and
|
||||
* {@code opt*}, which accept a default value and return that if the argument was not present or was {@code null}.
|
||||
* If the argument is of the wrong type, a suitable error message will be thrown, with a similar format to Lua's own
|
||||
* error messages.
|
||||
*
|
||||
* <h2>Example usage:</h2>
|
||||
* <pre>
|
||||
* {@code
|
||||
* int slot = getInt( args, 0 );
|
||||
* int amount = optInt( args, 1, 64 );
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public final class ArgumentHelper
|
||||
{
|
||||
private ArgumentHelper()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string representation of the given value's type.
|
||||
*
|
||||
* @param value The value whose type we are trying to compute.
|
||||
* @return A string representation of the given value's type, in a similar format to that provided by Lua's
|
||||
* {@code type} function.
|
||||
*/
|
||||
@Nonnull
|
||||
public static String getType( @Nullable Object value )
|
||||
{
|
||||
if( value == null ) return "nil";
|
||||
if( value instanceof String ) return "string";
|
||||
if( value instanceof Boolean ) return "boolean";
|
||||
if( value instanceof Number ) return "number";
|
||||
if( value instanceof Map ) return "table";
|
||||
return "userdata";
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a "bad argument" exception, from an expected type and the actual value provided.
|
||||
*
|
||||
* @param index The argument number, starting from 0.
|
||||
* @param expected The expected type for this argument.
|
||||
* @param actual The actual value provided for this argument.
|
||||
* @return The constructed exception, which should be thrown immediately.
|
||||
*/
|
||||
@Nonnull
|
||||
public static LuaException badArgumentOf( int index, @Nonnull String expected, @Nullable Object actual )
|
||||
{
|
||||
return badArgument( index, expected, getType( actual ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a "bad argument" exception, from an expected and actual type.
|
||||
*
|
||||
* @param index The argument number, starting from 0.
|
||||
* @param expected The expected type for this argument.
|
||||
* @param actual The provided type for this argument.
|
||||
* @return The constructed exception, which should be thrown immediately.
|
||||
*/
|
||||
@Nonnull
|
||||
public static LuaException badArgument( int index, @Nonnull String expected, @Nonnull String actual )
|
||||
{
|
||||
return new LuaException( "bad argument #" + (index + 1) + " (" + expected + " expected, got " + actual + ")" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a double.
|
||||
*
|
||||
* @param args The arguments to extract from.
|
||||
* @param index The index into the argument array to read from.
|
||||
* @return The argument's value.
|
||||
* @throws LuaException If the value is not a number.
|
||||
* @see #getFiniteDouble(Object[], int) if you require this to be finite (i.e. not infinite or NaN).
|
||||
*/
|
||||
public static double getDouble( @Nonnull Object[] args, int index ) throws LuaException
|
||||
{
|
||||
if( index >= args.length ) throw badArgument( index, "number", "nil" );
|
||||
Object value = args[index];
|
||||
if( !(value instanceof Number) ) throw badArgumentOf( index, "number", value );
|
||||
return ((Number) value).doubleValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as an integer.
|
||||
*
|
||||
* @param args The arguments to extract from.
|
||||
* @param index The index into the argument array to read from.
|
||||
* @return The argument's value.
|
||||
* @throws LuaException If the value is not an integer.
|
||||
*/
|
||||
public static int getInt( @Nonnull Object[] args, int index ) throws LuaException
|
||||
{
|
||||
return (int) getLong( args, index );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a long.
|
||||
*
|
||||
* @param args The arguments to extract from.
|
||||
* @param index The index into the argument array to read from.
|
||||
* @return The argument's value.
|
||||
* @throws LuaException If the value is not a long.
|
||||
*/
|
||||
public static long getLong( @Nonnull Object[] args, int index ) throws LuaException
|
||||
{
|
||||
if( index >= args.length ) throw badArgument( index, "number", "nil" );
|
||||
Object value = args[index];
|
||||
if( !(value instanceof Number) ) throw badArgumentOf( index, "number", value );
|
||||
return checkFinite( index, (Number) value ).longValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a finite number (not infinite or NaN).
|
||||
*
|
||||
* @param args The arguments to extract from.
|
||||
* @param index The index into the argument array to read from.
|
||||
* @return The argument's value.
|
||||
* @throws LuaException If the value is not finite.
|
||||
*/
|
||||
public static double getFiniteDouble( @Nonnull Object[] args, int index ) throws LuaException
|
||||
{
|
||||
return checkFinite( index, getDouble( args, index ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a boolean.
|
||||
*
|
||||
* @param args The arguments to extract from.
|
||||
* @param index The index into the argument array to read from.
|
||||
* @return The argument's value.
|
||||
* @throws LuaException If the value is not a boolean.
|
||||
*/
|
||||
public static boolean getBoolean( @Nonnull Object[] args, int index ) throws LuaException
|
||||
{
|
||||
if( index >= args.length ) throw badArgument( index, "boolean", "nil" );
|
||||
Object value = args[index];
|
||||
if( !(value instanceof Boolean) ) throw badArgumentOf( index, "boolean", value );
|
||||
return (Boolean) value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a string.
|
||||
*
|
||||
* @param args The arguments to extract from.
|
||||
* @param index The index into the argument array to read from.
|
||||
* @return The argument's value.
|
||||
* @throws LuaException If the value is not a string.
|
||||
*/
|
||||
@Nonnull
|
||||
public static String getString( @Nonnull Object[] args, int index ) throws LuaException
|
||||
{
|
||||
if( index >= args.length ) throw badArgument( index, "string", "nil" );
|
||||
Object value = args[index];
|
||||
if( !(value instanceof String) ) throw badArgumentOf( index, "string", value );
|
||||
return (String) value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a table.
|
||||
*
|
||||
* @param args The arguments to extract from.
|
||||
* @param index The index into the argument array to read from.
|
||||
* @return The argument's value.
|
||||
* @throws LuaException If the value is not a table.
|
||||
*/
|
||||
@Nonnull
|
||||
public static Map<?, ?> getTable( @Nonnull Object[] args, int index ) throws LuaException
|
||||
{
|
||||
if( index >= args.length ) throw badArgument( index, "table", "nil" );
|
||||
Object value = args[index];
|
||||
if( !(value instanceof Map) ) throw badArgumentOf( index, "table", value );
|
||||
return (Map<?, ?>) value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a double.
|
||||
*
|
||||
* @param args The arguments to extract from.
|
||||
* @param index The index into the argument array to read from.
|
||||
* @param def The default value, if this argument is not given.
|
||||
* @return The argument's value, or {@code def} if none was provided.
|
||||
* @throws LuaException If the value is not a number.
|
||||
*/
|
||||
public static double optDouble( @Nonnull Object[] args, int index, double def ) throws LuaException
|
||||
{
|
||||
Object value = index < args.length ? args[index] : null;
|
||||
if( value == null ) return def;
|
||||
if( !(value instanceof Number) ) throw badArgumentOf( index, "number", value );
|
||||
return ((Number) value).doubleValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as an int.
|
||||
*
|
||||
* @param args The arguments to extract from.
|
||||
* @param index The index into the argument array to read from.
|
||||
* @param def The default value, if this argument is not given.
|
||||
* @return The argument's value, or {@code def} if none was provided.
|
||||
* @throws LuaException If the value is not a number.
|
||||
*/
|
||||
public static int optInt( @Nonnull Object[] args, int index, int def ) throws LuaException
|
||||
{
|
||||
return (int) optLong( args, index, def );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a long.
|
||||
*
|
||||
* @param args The arguments to extract from.
|
||||
* @param index The index into the argument array to read from.
|
||||
* @param def The default value, if this argument is not given.
|
||||
* @return The argument's value, or {@code def} if none was provided.
|
||||
* @throws LuaException If the value is not a number.
|
||||
*/
|
||||
public static long optLong( @Nonnull Object[] args, int index, long def ) throws LuaException
|
||||
{
|
||||
Object value = index < args.length ? args[index] : null;
|
||||
if( value == null ) return def;
|
||||
if( !(value instanceof Number) ) throw badArgumentOf( index, "number", value );
|
||||
return checkFinite( index, (Number) value ).longValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a finite number (not infinite or NaN).
|
||||
*
|
||||
* @param args The arguments to extract from.
|
||||
* @param index The index into the argument array to read from.
|
||||
* @param def The default value, if this argument is not given.
|
||||
* @return The argument's value, or {@code def} if none was provided.
|
||||
* @throws LuaException If the value is not finite.
|
||||
*/
|
||||
public static double optFiniteDouble( @Nonnull Object[] args, int index, double def ) throws LuaException
|
||||
{
|
||||
return checkFinite( index, optDouble( args, index, def ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a boolean.
|
||||
*
|
||||
* @param args The arguments to extract from.
|
||||
* @param index The index into the argument array to read from.
|
||||
* @param def The default value, if this argument is not given.
|
||||
* @return The argument's value, or {@code def} if none was provided.
|
||||
* @throws LuaException If the value is not a boolean.
|
||||
*/
|
||||
public static boolean optBoolean( @Nonnull Object[] args, int index, boolean def ) throws LuaException
|
||||
{
|
||||
Object value = index < args.length ? args[index] : null;
|
||||
if( value == null ) return def;
|
||||
if( !(value instanceof Boolean) ) throw badArgumentOf( index, "boolean", value );
|
||||
return (Boolean) value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a string.
|
||||
*
|
||||
* @param args The arguments to extract from.
|
||||
* @param index The index into the argument array to read from.
|
||||
* @param def The default value, if this argument is not given.
|
||||
* @return The argument's value, or {@code def} if none was provided.
|
||||
* @throws LuaException If the value is not a string.
|
||||
*/
|
||||
public static String optString( @Nonnull Object[] args, int index, String def ) throws LuaException
|
||||
{
|
||||
Object value = index < args.length ? args[index] : null;
|
||||
if( value == null ) return def;
|
||||
if( !(value instanceof String) ) throw badArgumentOf( index, "string", value );
|
||||
return (String) value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a table.
|
||||
*
|
||||
* @param args The arguments to extract from.
|
||||
* @param index The index into the argument array to read from.
|
||||
* @param def The default value, if this argument is not given.
|
||||
* @return The argument's value, or {@code def} if none was provided.
|
||||
* @throws LuaException If the value is not a table.
|
||||
*/
|
||||
public static Map<?, ?> optTable( @Nonnull Object[] args, int index, Map<Object, Object> def ) throws LuaException
|
||||
{
|
||||
Object value = index < args.length ? args[index] : null;
|
||||
if( value == null ) return def;
|
||||
if( !(value instanceof Map) ) throw badArgumentOf( index, "table", value );
|
||||
return (Map<?, ?>) value;
|
||||
}
|
||||
|
||||
private static Number checkFinite( int index, Number value ) throws LuaException
|
||||
{
|
||||
checkFinite( index, value.doubleValue() );
|
||||
return value;
|
||||
}
|
||||
|
||||
private static double checkFinite( int index, double value ) throws LuaException
|
||||
{
|
||||
if( !Double.isFinite( value ) ) throw badArgument( index, "number", getNumericType( value ) );
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a more detailed representation of this number's type. If this is finite, it will just return "number",
|
||||
* otherwise it returns whether it is infinite or NaN.
|
||||
*
|
||||
* @param value The value to extract the type for.
|
||||
* @return This value's numeric type.
|
||||
*/
|
||||
@Nonnull
|
||||
public static String getNumericType( double value )
|
||||
{
|
||||
if( Double.isNaN( value ) ) return "nan";
|
||||
if( value == Double.POSITIVE_INFINITY ) return "inf";
|
||||
if( value == Double.NEGATIVE_INFINITY ) return "-inf";
|
||||
return "number";
|
||||
}
|
||||
}
|
||||
407
src/main/java/dan200/computercraft/api/lua/IArguments.java
Normal file
407
src/main/java/dan200/computercraft/api/lua/IArguments.java
Normal file
@@ -0,0 +1,407 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
|
||||
* For help using the API, and posting your mods, visit the forums at computercraft.info.
|
||||
*/
|
||||
package dan200.computercraft.api.lua;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static dan200.computercraft.api.lua.LuaValues.checkFinite;
|
||||
|
||||
/**
|
||||
* The arguments passed to a function.
|
||||
*/
|
||||
public interface IArguments
|
||||
{
|
||||
/**
|
||||
* Get the number of arguments passed to this function.
|
||||
*
|
||||
* @return The number of passed arguments.
|
||||
*/
|
||||
int count();
|
||||
|
||||
/**
|
||||
* Get the argument at the specific index. The returned value must obey the following conversion rules:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Lua values of type "string" will be represented by a {@link String}.</li>
|
||||
* <li>Lua values of type "number" will be represented by a {@link Number}.</li>
|
||||
* <li>Lua values of type "boolean" will be represented by a {@link Boolean}.</li>
|
||||
* <li>Lua values of type "table" will be represented by a {@link Map}.</li>
|
||||
* <li>Lua values of any other type will be represented by a {@code null} value.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @return The argument's value, or {@code null} if not present.
|
||||
*/
|
||||
@Nullable
|
||||
Object get( int index );
|
||||
|
||||
/**
|
||||
* Drop a number of arguments. The returned arguments instance will access arguments at position {@code i + count},
|
||||
* rather than {@code i}. However, errors will still use the given argument index.
|
||||
*
|
||||
* @param count The number of arguments to drop.
|
||||
* @return The new {@link IArguments} instance.
|
||||
*/
|
||||
IArguments drop( int count );
|
||||
|
||||
default Object[] getAll()
|
||||
{
|
||||
Object[] result = new Object[count()];
|
||||
for( int i = 0; i < result.length; i++ ) result[i] = get( i );
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a double.
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @return The argument's value.
|
||||
* @throws LuaException If the value is not a number.
|
||||
* @see #getFiniteDouble(int) if you require this to be finite (i.e. not infinite or NaN).
|
||||
*/
|
||||
default double getDouble( int index ) throws LuaException
|
||||
{
|
||||
Object value = get( index );
|
||||
if( !(value instanceof Number) ) throw LuaValues.badArgumentOf( index, "number", value );
|
||||
return ((Number) value).doubleValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as an integer.
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @return The argument's value.
|
||||
* @throws LuaException If the value is not an integer.
|
||||
*/
|
||||
default int getInt( int index ) throws LuaException
|
||||
{
|
||||
return (int) getLong( index );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a long.
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @return The argument's value.
|
||||
* @throws LuaException If the value is not a long.
|
||||
*/
|
||||
default long getLong( int index ) throws LuaException
|
||||
{
|
||||
Object value = get( index );
|
||||
if( !(value instanceof Number) ) throw LuaValues.badArgumentOf( index, "number", value );
|
||||
return LuaValues.checkFiniteNum( index, (Number) value ).longValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a finite number (not infinite or NaN).
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @return The argument's value.
|
||||
* @throws LuaException If the value is not finite.
|
||||
*/
|
||||
default double getFiniteDouble( int index ) throws LuaException
|
||||
{
|
||||
return checkFinite( index, getDouble( index ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a boolean.
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @return The argument's value.
|
||||
* @throws LuaException If the value is not a boolean.
|
||||
*/
|
||||
default boolean getBoolean( int index ) throws LuaException
|
||||
{
|
||||
Object value = get( index );
|
||||
if( !(value instanceof Boolean) ) throw LuaValues.badArgumentOf( index, "boolean", value );
|
||||
return (Boolean) value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a string.
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @return The argument's value.
|
||||
* @throws LuaException If the value is not a string.
|
||||
*/
|
||||
@Nonnull
|
||||
default String getString( int index ) throws LuaException
|
||||
{
|
||||
Object value = get( index );
|
||||
if( !(value instanceof String) ) throw LuaValues.badArgumentOf( index, "string", value );
|
||||
return (String) value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string argument as a byte array.
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @return The argument's value. This is a <em>read only</em> buffer.
|
||||
* @throws LuaException If the value is not a string.
|
||||
*/
|
||||
@Nonnull
|
||||
default ByteBuffer getBytes( int index ) throws LuaException
|
||||
{
|
||||
return LuaValues.encode( getString( index ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string argument as an enum value.
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @param klass The type of enum to parse.
|
||||
* @param <T> The type of enum to parse.
|
||||
* @return The argument's value.
|
||||
* @throws LuaException If the value is not a string or not a valid option for this enum.
|
||||
*/
|
||||
@Nonnull
|
||||
default <T extends Enum<T>> T getEnum( int index, Class<T> klass ) throws LuaException
|
||||
{
|
||||
return LuaValues.checkEnum( index, klass, getString( index ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a table.
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @return The argument's value.
|
||||
* @throws LuaException If the value is not a table.
|
||||
*/
|
||||
@Nonnull
|
||||
default Map<?, ?> getTable( int index ) throws LuaException
|
||||
{
|
||||
Object value = get( index );
|
||||
if( !(value instanceof Map) ) throw LuaValues.badArgumentOf( index, "table", value );
|
||||
return (Map<?, ?>) value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a double.
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @return The argument's value, or {@link Optional#empty()} if not present.
|
||||
* @throws LuaException If the value is not a number.
|
||||
*/
|
||||
@Nonnull
|
||||
default Optional<Double> optDouble( int index ) throws LuaException
|
||||
{
|
||||
Object value = get( index );
|
||||
if( value == null ) return Optional.empty();
|
||||
if( !(value instanceof Number) ) throw LuaValues.badArgumentOf( index, "number", value );
|
||||
return Optional.of( ((Number) value).doubleValue() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as an int.
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @return The argument's value, or {@link Optional#empty()} if not present.
|
||||
* @throws LuaException If the value is not a number.
|
||||
*/
|
||||
@Nonnull
|
||||
default Optional<Integer> optInt( int index ) throws LuaException
|
||||
{
|
||||
return optLong( index ).map( Long::intValue );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a long.
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @return The argument's value, or {@link Optional#empty()} if not present.
|
||||
* @throws LuaException If the value is not a number.
|
||||
*/
|
||||
default Optional<Long> optLong( int index ) throws LuaException
|
||||
{
|
||||
Object value = get( index );
|
||||
if( value == null ) return Optional.empty();
|
||||
if( !(value instanceof Number) ) throw LuaValues.badArgumentOf( index, "number", value );
|
||||
return Optional.of( LuaValues.checkFiniteNum( index, (Number) value ).longValue() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a finite number (not infinite or NaN).
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @return The argument's value, or {@link Optional#empty()} if not present.
|
||||
* @throws LuaException If the value is not finite.
|
||||
*/
|
||||
default Optional<Double> optFiniteDouble( int index ) throws LuaException
|
||||
{
|
||||
Optional<Double> value = optDouble( index );
|
||||
if( value.isPresent() ) LuaValues.checkFiniteNum( index, value.get() );
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a boolean.
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @return The argument's value, or {@link Optional#empty()} if not present.
|
||||
* @throws LuaException If the value is not a boolean.
|
||||
*/
|
||||
default Optional<Boolean> optBoolean( int index ) throws LuaException
|
||||
{
|
||||
Object value = get( index );
|
||||
if( value == null ) return Optional.empty();
|
||||
if( !(value instanceof Boolean) ) throw LuaValues.badArgumentOf( index, "boolean", value );
|
||||
return Optional.of( (Boolean) value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a string.
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @return The argument's value, or {@link Optional#empty()} if not present.
|
||||
* @throws LuaException If the value is not a string.
|
||||
*/
|
||||
default Optional<String> optString( int index ) throws LuaException
|
||||
{
|
||||
Object value = get( index );
|
||||
if( value == null ) return Optional.empty();
|
||||
if( !(value instanceof String) ) throw LuaValues.badArgumentOf( index, "string", value );
|
||||
return Optional.of( (String) value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string argument as a byte array.
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @return The argument's value, or {@link Optional#empty()} if not present. This is a <em>read only</em> buffer.
|
||||
* @throws LuaException If the value is not a string.
|
||||
*/
|
||||
default Optional<ByteBuffer> optBytes( int index ) throws LuaException
|
||||
{
|
||||
return optString( index ).map( LuaValues::encode );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string argument as an enum value.
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @param klass The type of enum to parse.
|
||||
* @param <T> The type of enum to parse.
|
||||
* @return The argument's value.
|
||||
* @throws LuaException If the value is not a string or not a valid option for this enum.
|
||||
*/
|
||||
@Nonnull
|
||||
default <T extends Enum<T>> Optional<T> optEnum( int index, Class<T> klass ) throws LuaException
|
||||
{
|
||||
Optional<String> str = optString( index );
|
||||
return str.isPresent() ? Optional.of( LuaValues.checkEnum( index, klass, str.get() ) ) : Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a table.
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @return The argument's value, or {@link Optional#empty()} if not present.
|
||||
* @throws LuaException If the value is not a table.
|
||||
*/
|
||||
default Optional<Map<?, ?>> optTable( int index ) throws LuaException
|
||||
{
|
||||
Object value = get( index );
|
||||
if( value == null ) return Optional.empty();
|
||||
if( !(value instanceof Map) ) throw LuaValues.badArgumentOf( index, "map", value );
|
||||
return Optional.of( (Map<?, ?>) value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a double.
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @param def The default value, if this argument is not given.
|
||||
* @return The argument's value, or {@code def} if none was provided.
|
||||
* @throws LuaException If the value is not a number.
|
||||
*/
|
||||
default double optDouble( int index, double def ) throws LuaException
|
||||
{
|
||||
return optDouble( index ).orElse( def );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as an int.
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @param def The default value, if this argument is not given.
|
||||
* @return The argument's value, or {@code def} if none was provided.
|
||||
* @throws LuaException If the value is not a number.
|
||||
*/
|
||||
default int optInt( int index, int def ) throws LuaException
|
||||
{
|
||||
return optInt( index ).orElse( def );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a long.
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @param def The default value, if this argument is not given.
|
||||
* @return The argument's value, or {@code def} if none was provided.
|
||||
* @throws LuaException If the value is not a number.
|
||||
*/
|
||||
default long optLong( int index, long def ) throws LuaException
|
||||
{
|
||||
return optLong( index ).orElse( def );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a finite number (not infinite or NaN).
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @param def The default value, if this argument is not given.
|
||||
* @return The argument's value, or {@code def} if none was provided.
|
||||
* @throws LuaException If the value is not finite.
|
||||
*/
|
||||
default double optFiniteDouble( int index, double def ) throws LuaException
|
||||
{
|
||||
return optFiniteDouble( index ).orElse( def );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a boolean.
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @param def The default value, if this argument is not given.
|
||||
* @return The argument's value, or {@code def} if none was provided.
|
||||
* @throws LuaException If the value is not a boolean.
|
||||
*/
|
||||
default boolean optBoolean( int index, boolean def ) throws LuaException
|
||||
{
|
||||
return optBoolean( index ).orElse( def );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a string.
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @param def The default value, if this argument is not given.
|
||||
* @return The argument's value, or {@code def} if none was provided.
|
||||
* @throws LuaException If the value is not a string.
|
||||
*/
|
||||
default String optString( int index, String def ) throws LuaException
|
||||
{
|
||||
return optString( index ).orElse( def );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an argument as a table.
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @param def The default value, if this argument is not given.
|
||||
* @return The argument's value, or {@code def} if none was provided.
|
||||
* @throws LuaException If the value is not a table.
|
||||
*/
|
||||
default Map<?, ?> optTable( int index, Map<Object, Object> def ) throws LuaException
|
||||
{
|
||||
return optTable( index ).orElse( def );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
|
||||
* For help using the API, and posting your mods, visit the forums at computercraft.info.
|
||||
*/
|
||||
package dan200.computercraft.api.lua;
|
||||
|
||||
import dan200.computercraft.api.peripheral.IDynamicPeripheral;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* An interface for representing custom objects returned by peripherals or other Lua objects.
|
||||
*
|
||||
* Generally, one does not need to implement this type - it is sufficient to return an object with some methods
|
||||
* annotated with {@link LuaFunction}. {@link IDynamicLuaObject} is useful when you wish your available methods to
|
||||
* change at runtime.
|
||||
*/
|
||||
public interface IDynamicLuaObject
|
||||
{
|
||||
/**
|
||||
* Get the names of the methods that this object implements. This should not change over the course of the object's
|
||||
* lifetime.
|
||||
*
|
||||
* @return The method names this object provides.
|
||||
* @see IDynamicPeripheral#getMethodNames()
|
||||
*/
|
||||
@Nonnull
|
||||
String[] getMethodNames();
|
||||
|
||||
/**
|
||||
* Called when a user calls one of the methods that this object implements.
|
||||
*
|
||||
* @param context The context of the currently running lua thread. This can be used to wait for events
|
||||
* or otherwise yield.
|
||||
* @param method An integer identifying which method index from {@link #getMethodNames()} the computer wishes
|
||||
* to call.
|
||||
* @param arguments The arguments for this method.
|
||||
* @return The result of this function. Either an immediate value ({@link MethodResult#of(Object...)} or an
|
||||
* instruction to yield.
|
||||
* @throws LuaException If the function threw an exception.
|
||||
*/
|
||||
@Nonnull
|
||||
MethodResult callMethod( @Nonnull ILuaContext context, int method, @Nonnull IArguments arguments ) throws LuaException;
|
||||
}
|
||||
@@ -8,7 +8,8 @@ package dan200.computercraft.api.lua;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
|
||||
/**
|
||||
* Represents a {@link ILuaObject} which is stored as a global variable on computer startup.
|
||||
* Represents a Lua object which is stored as a global variable on computer startup. This must either provide
|
||||
* {@link LuaFunction} annotated functions or implement {@link IDynamicLuaObject}.
|
||||
*
|
||||
* Before implementing this interface, consider alternative methods of providing methods. It is generally preferred
|
||||
* to use peripherals to provide functionality to users.
|
||||
@@ -16,7 +17,7 @@ import dan200.computercraft.api.ComputerCraftAPI;
|
||||
* @see ILuaAPIFactory
|
||||
* @see ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory)
|
||||
*/
|
||||
public interface ILuaAPI extends ILuaObject
|
||||
public interface ILuaAPI
|
||||
{
|
||||
/**
|
||||
* Get the globals this API will be assigned to. This will override any other global, so you should
|
||||
|
||||
27
src/main/java/dan200/computercraft/api/lua/ILuaCallback.java
Normal file
27
src/main/java/dan200/computercraft/api/lua/ILuaCallback.java
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
|
||||
* For help using the API, and posting your mods, visit the forums at computercraft.info.
|
||||
*/
|
||||
package dan200.computercraft.api.lua;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* A continuation which is called when this coroutine is resumed.
|
||||
*
|
||||
* @see MethodResult#yield(Object[], ILuaCallback)
|
||||
*/
|
||||
public interface ILuaCallback
|
||||
{
|
||||
/**
|
||||
* Resume this coroutine.
|
||||
*
|
||||
* @param args The result of resuming this coroutine. These will have the same form as described in
|
||||
* {@link LuaFunction}.
|
||||
* @return The result of this continuation. Either the result to return to the callee, or another yield.
|
||||
* @throws LuaException On an error.
|
||||
*/
|
||||
@Nonnull
|
||||
MethodResult resume( Object[] args ) throws LuaException;
|
||||
}
|
||||
@@ -6,99 +6,25 @@
|
||||
package dan200.computercraft.api.lua;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* An interface passed to peripherals and {@link ILuaObject}s by computers or turtles, providing methods
|
||||
* that allow the peripheral call to wait for events before returning, just like in lua. This is very useful if you need
|
||||
* to signal work to be performed on the main thread, and don't want to return until the work has been completed.
|
||||
* An interface passed to peripherals and {@link IDynamicLuaObject}s by computers or turtles, providing methods
|
||||
* that allow the peripheral call to interface with the computer.
|
||||
*/
|
||||
public interface ILuaContext
|
||||
{
|
||||
/**
|
||||
* Wait for an event to occur on the computer, suspending the thread until it arises. This method is exactly
|
||||
* equivalent to {@code os.pullEvent()} in lua.
|
||||
*
|
||||
* @param filter A specific event to wait for, or null to wait for any event.
|
||||
* @return An object array containing the name of the event that occurred, and any event parameters.
|
||||
* @throws LuaException If the user presses CTRL+T to terminate the current program while pullEvent() is
|
||||
* waiting for an event, a "Terminated" exception will be thrown here.
|
||||
*
|
||||
* Do not attempt to catch this exception. You should use {@link #pullEventRaw(String)}
|
||||
* should you wish to disable termination.
|
||||
* @throws InterruptedException If the user shuts down or reboots the computer while pullEvent() is waiting for an
|
||||
* event, InterruptedException will be thrown. This exception must not be caught or
|
||||
* intercepted, or the computer will leak memory and end up in a broken state.
|
||||
*/
|
||||
@Nonnull
|
||||
default Object[] pullEvent( @Nullable String filter ) throws LuaException, InterruptedException
|
||||
{
|
||||
Object[] results = pullEventRaw( filter );
|
||||
if( results.length >= 1 && results[0].equals( "terminate" ) ) throw new LuaException( "Terminated", 0 );
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* The same as {@link #pullEvent(String)}, except "terminated" events are ignored. Only use this if you want to
|
||||
* prevent program termination, which is not recommended. This method is exactly equivalent to
|
||||
* {@code os.pullEventRaw()} in lua.
|
||||
*
|
||||
* @param filter A specific event to wait for, or null to wait for any event.
|
||||
* @return An object array containing the name of the event that occurred, and any event parameters.
|
||||
* @throws InterruptedException If the user shuts down or reboots the computer while pullEventRaw() is waiting for
|
||||
* an event, InterruptedException will be thrown. This exception must not be caught or
|
||||
* intercepted, or the computer will leak memory and end up in a broken state.
|
||||
* @see #pullEvent(String)
|
||||
*/
|
||||
@Nonnull
|
||||
default Object[] pullEventRaw( @Nullable String filter ) throws InterruptedException
|
||||
{
|
||||
return yield( new Object[] { filter } );
|
||||
}
|
||||
|
||||
/**
|
||||
* Yield the current coroutine with some arguments until it is resumed. This method is exactly equivalent to
|
||||
* {@code coroutine.yield()} in lua. Use {@code pullEvent()} if you wish to wait for events.
|
||||
*
|
||||
* @param arguments An object array containing the arguments to pass to coroutine.yield()
|
||||
* @return An object array containing the return values from coroutine.yield()
|
||||
* @throws InterruptedException If the user shuts down or reboots the computer the coroutine is suspended,
|
||||
* InterruptedException will be thrown. This exception must not be caught or
|
||||
* intercepted, or the computer will leak memory and end up in a broken state.
|
||||
* @see #pullEvent(String)
|
||||
*/
|
||||
@Nonnull
|
||||
Object[] yield( @Nullable Object[] arguments ) throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Queue a task to be executed on the main server thread at the beginning of next tick, waiting for it to complete.
|
||||
* This should be used when you need to interact with the world in a thread-safe manner.
|
||||
*
|
||||
* Note that the return values of your task are handled as events, meaning more complex objects such as maps or
|
||||
* {@link ILuaObject} will not preserve their identities.
|
||||
*
|
||||
* @param task The task to execute on the main thread.
|
||||
* @return The objects returned by {@code task}.
|
||||
* @throws LuaException If the task could not be queued, or if the task threw an exception.
|
||||
* @throws InterruptedException If the user shuts down or reboots the computer the coroutine is suspended,
|
||||
* InterruptedException will be thrown. This exception must not be caught or
|
||||
* intercepted, or the computer will leak memory and end up in a broken state.
|
||||
*/
|
||||
@Nullable
|
||||
Object[] executeMainThreadTask( @Nonnull ILuaTask task ) throws LuaException, InterruptedException;
|
||||
|
||||
/**
|
||||
* Queue a task to be executed on the main server thread at the beginning of next tick, but do not wait for it to
|
||||
* complete. This should be used when you need to interact with the world in a thread-safe manner but do not care
|
||||
* about the result or you wish to run asynchronously.
|
||||
*
|
||||
* When the task has finished, it will enqueue a {@code task_completed} event, which takes the task id, a success
|
||||
* value and the return values, or an error message if it failed. If you need to wait on this event, it may be
|
||||
* better to use {@link #executeMainThreadTask(ILuaTask)}.
|
||||
* value and the return values, or an error message if it failed.
|
||||
*
|
||||
* @param task The task to execute on the main thread.
|
||||
* @return The "id" of the task. This will be the first argument to the {@code task_completed} event.
|
||||
* @throws LuaException If the task could not be queued.
|
||||
* @see LuaFunction#mainThread() To run functions on the main thread and return their results synchronously.
|
||||
*/
|
||||
long issueMainThreadTask( @Nonnull ILuaTask task ) throws LuaException;
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
|
||||
* For help using the API, and posting your mods, visit the forums at computercraft.info.
|
||||
*/
|
||||
package dan200.computercraft.api.lua;
|
||||
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* An interface for representing custom objects returned by {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}
|
||||
* calls.
|
||||
*
|
||||
* Return objects implementing this interface to expose objects with methods to lua.
|
||||
*/
|
||||
public interface ILuaObject
|
||||
{
|
||||
/**
|
||||
* Get the names of the methods that this object implements. This works the same as {@link IPeripheral#getMethodNames()}.
|
||||
* See that method for detailed documentation.
|
||||
*
|
||||
* @return The method names this object provides.
|
||||
* @see IPeripheral#getMethodNames()
|
||||
*/
|
||||
@Nonnull
|
||||
String[] getMethodNames();
|
||||
|
||||
/**
|
||||
* Called when a user calls one of the methods that this object implements. This works the same as
|
||||
* {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}}. See that method for detailed
|
||||
* documentation.
|
||||
*
|
||||
* @param context The context of the currently running lua thread. This can be used to wait for events
|
||||
* or otherwise yield.
|
||||
* @param method An integer identifying which of the methods from getMethodNames() the computercraft
|
||||
* wishes to call. The integer indicates the index into the getMethodNames() table
|
||||
* that corresponds to the string passed into peripheral.call()
|
||||
* @param arguments The arguments for this method. See {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}
|
||||
* the possible values and conversion rules.
|
||||
* @return An array of objects, representing the values you wish to return to the Lua program.
|
||||
* See {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])} for the valid values and
|
||||
* conversion rules.
|
||||
* @throws LuaException If the task could not be queued, or if the task threw an exception.
|
||||
* @throws InterruptedException If the user shuts down or reboots the computer the coroutine is suspended,
|
||||
* InterruptedException will be thrown. This exception must not be caught or
|
||||
* intercepted, or the computer will leak memory and end up in a broken state.w
|
||||
* @see IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])
|
||||
*/
|
||||
@Nullable
|
||||
Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException;
|
||||
}
|
||||
@@ -8,11 +8,10 @@ package dan200.computercraft.api.lua;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A task which can be executed via {@link ILuaContext#executeMainThreadTask(ILuaTask)} or
|
||||
* {@link ILuaContext#issueMainThreadTask(ILuaTask)}. This will be run on the main thread, at the beginning of the
|
||||
* A task which can be executed via {@link ILuaContext#issueMainThreadTask(ILuaTask)} This will be run on the main
|
||||
* thread, at the beginning of the
|
||||
* next tick.
|
||||
*
|
||||
* @see ILuaContext#executeMainThreadTask(ILuaTask)
|
||||
* @see ILuaContext#issueMainThreadTask(ILuaTask)
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@@ -21,8 +20,7 @@ public interface ILuaTask
|
||||
/**
|
||||
* Execute this task.
|
||||
*
|
||||
* @return The arguments to add to the {@code task_completed} event. These will be returned by
|
||||
* {@link ILuaContext#executeMainThreadTask(ILuaTask)}.
|
||||
* @return The arguments to add to the {@code task_completed} event.
|
||||
* @throws LuaException If you throw any exception from this function, a lua error will be raised with the
|
||||
* same message as your exception. Use this to throw appropriate errors if the wrong
|
||||
* arguments are supplied to your method.
|
||||
|
||||
@@ -13,24 +13,33 @@ import javax.annotation.Nullable;
|
||||
public class LuaException extends Exception
|
||||
{
|
||||
private static final long serialVersionUID = -6136063076818512651L;
|
||||
private final boolean hasLevel;
|
||||
private final int level;
|
||||
|
||||
public LuaException()
|
||||
{
|
||||
this( "error", 1 );
|
||||
}
|
||||
|
||||
public LuaException( @Nullable String message )
|
||||
{
|
||||
this( message, 1 );
|
||||
super( message );
|
||||
this.hasLevel = false;
|
||||
this.level = 1;
|
||||
}
|
||||
|
||||
public LuaException( @Nullable String message, int level )
|
||||
{
|
||||
super( message );
|
||||
this.hasLevel = true;
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a level was explicitly specified when constructing. This is used to determine
|
||||
*
|
||||
* @return Whether this has an explicit level.
|
||||
*/
|
||||
public boolean hasLevel()
|
||||
{
|
||||
return hasLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* The level this error is raised at. Level 1 is the function's caller, level 2 is that function's caller, and so
|
||||
* on.
|
||||
|
||||
58
src/main/java/dan200/computercraft/api/lua/LuaFunction.java
Normal file
58
src/main/java/dan200/computercraft/api/lua/LuaFunction.java
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
|
||||
* For help using the API, and posting your mods, visit the forums at computercraft.info.
|
||||
*/
|
||||
package dan200.computercraft.api.lua;
|
||||
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Used to mark a Java function which is callable from Lua.
|
||||
*
|
||||
* Methods annotated with {@link LuaFunction} must be public final instance methods. They can have any number of
|
||||
* parameters, but they must be of the following types:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link ILuaContext} (and {@link IComputerAccess} if on a {@link IPeripheral})</li>
|
||||
* <li>{@link IArguments}: The arguments supplied to this function.</li>
|
||||
* <li>
|
||||
* Alternatively, one may specify the desired arguments as normal parameters and the argument parsing code will
|
||||
* be generated automatically.
|
||||
*
|
||||
* Each parameter must be one of the given types supported by {@link IArguments} (for instance, {@link int} or
|
||||
* {@link Map}). Optional values are supported by accepting a parameter of type {@link Optional}.
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* This function may return {@link MethodResult}. However, if you simply return a value (rather than having to yield),
|
||||
* you may return {@code void}, a single value (either an object or a primitive like {@code int}) or array of objects.
|
||||
* These will be treated the same as {@link MethodResult#of()}, {@link MethodResult#of(Object)} and
|
||||
* {@link MethodResult#of(Object...)}.
|
||||
*/
|
||||
@Documented
|
||||
@Retention( RetentionPolicy.RUNTIME )
|
||||
@Target( ElementType.METHOD )
|
||||
public @interface LuaFunction
|
||||
{
|
||||
/**
|
||||
* Explicitly specify the method names of this function. If not given, it uses the name of the annotated method.
|
||||
*
|
||||
* @return This function's name(s).
|
||||
*/
|
||||
String[] value() default {};
|
||||
|
||||
/**
|
||||
* Run this function on the main server thread. This should be specified for any method which interacts with
|
||||
* Minecraft in a thread-unsafe manner.
|
||||
*
|
||||
* @return Whether this functi
|
||||
* @see ILuaContext#issueMainThreadTask(ILuaTask)
|
||||
*/
|
||||
boolean mainThread() default false;
|
||||
}
|
||||
152
src/main/java/dan200/computercraft/api/lua/LuaValues.java
Normal file
152
src/main/java/dan200/computercraft/api/lua/LuaValues.java
Normal file
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
|
||||
* For help using the API, and posting your mods, visit the forums at computercraft.info.
|
||||
*/
|
||||
package dan200.computercraft.api.lua;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Various utility functions for operating with Lua values.
|
||||
*
|
||||
* @see IArguments
|
||||
*/
|
||||
public final class LuaValues
|
||||
{
|
||||
private LuaValues()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a Lua string into a read-only {@link ByteBuffer}.
|
||||
*
|
||||
* @param string The string to encode.
|
||||
* @return The encoded string.
|
||||
*/
|
||||
@Nonnull
|
||||
public static ByteBuffer encode( @Nonnull String string )
|
||||
{
|
||||
byte[] chars = new byte[string.length()];
|
||||
for( int i = 0; i < chars.length; i++ )
|
||||
{
|
||||
char c = string.charAt( i );
|
||||
chars[i] = c < 256 ? (byte) c : 63;
|
||||
}
|
||||
|
||||
return ByteBuffer.wrap( chars ).asReadOnlyBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a more detailed representation of this number's type. If this is finite, it will just return "number",
|
||||
* otherwise it returns whether it is infinite or NaN.
|
||||
*
|
||||
* @param value The value to extract the type for.
|
||||
* @return This value's numeric type.
|
||||
*/
|
||||
@Nonnull
|
||||
public static String getNumericType( double value )
|
||||
{
|
||||
if( Double.isNaN( value ) ) return "nan";
|
||||
if( value == Double.POSITIVE_INFINITY ) return "inf";
|
||||
if( value == Double.NEGATIVE_INFINITY ) return "-inf";
|
||||
return "number";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string representation of the given value's type.
|
||||
*
|
||||
* @param value The value whose type we are trying to compute.
|
||||
* @return A string representation of the given value's type, in a similar format to that provided by Lua's
|
||||
* {@code type} function.
|
||||
*/
|
||||
@Nonnull
|
||||
public static String getType( @Nullable Object value )
|
||||
{
|
||||
if( value == null ) return "nil";
|
||||
if( value instanceof String ) return "string";
|
||||
if( value instanceof Boolean ) return "boolean";
|
||||
if( value instanceof Number ) return "number";
|
||||
if( value instanceof Map ) return "table";
|
||||
return "userdata";
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a "bad argument" exception, from an expected type and the actual value provided.
|
||||
*
|
||||
* @param index The argument number, starting from 0.
|
||||
* @param expected The expected type for this argument.
|
||||
* @param actual The actual value provided for this argument.
|
||||
* @return The constructed exception, which should be thrown immediately.
|
||||
*/
|
||||
@Nonnull
|
||||
public static LuaException badArgumentOf( int index, @Nonnull String expected, @Nullable Object actual )
|
||||
{
|
||||
return badArgument( index, expected, getType( actual ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a "bad argument" exception, from an expected and actual type.
|
||||
*
|
||||
* @param index The argument number, starting from 0.
|
||||
* @param expected The expected type for this argument.
|
||||
* @param actual The provided type for this argument.
|
||||
* @return The constructed exception, which should be thrown immediately.
|
||||
*/
|
||||
@Nonnull
|
||||
public static LuaException badArgument( int index, @Nonnull String expected, @Nonnull String actual )
|
||||
{
|
||||
return new LuaException( "bad argument #" + (index + 1) + " (" + expected + " expected, got " + actual + ")" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure a numeric argument is finite (i.e. not infinite or {@link Double#NaN}.
|
||||
*
|
||||
* @param index The argument index to check.
|
||||
* @param value The value to check.
|
||||
* @return The input {@code value}.
|
||||
* @throws LuaException If this is not a finite number.
|
||||
*/
|
||||
public static Number checkFiniteNum( int index, Number value ) throws LuaException
|
||||
{
|
||||
checkFinite( index, value.doubleValue() );
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure a numeric argument is finite (i.e. not infinite or {@link Double#NaN}.
|
||||
*
|
||||
* @param index The argument index to check.
|
||||
* @param value The value to check.
|
||||
* @return The input {@code value}.
|
||||
* @throws LuaException If this is not a finite number.
|
||||
*/
|
||||
public static double checkFinite( int index, double value ) throws LuaException
|
||||
{
|
||||
if( !Double.isFinite( value ) ) throw badArgument( index, "number", getNumericType( value ) );
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure a string is a valid enum value.
|
||||
*
|
||||
* @param index The argument index to check.
|
||||
* @param klass The class of the enum instance.
|
||||
* @param value The value to extract.
|
||||
* @param <T> The type of enum we are extracting.
|
||||
* @return The parsed enum value.
|
||||
* @throws LuaException If this is not a known enum value.
|
||||
*/
|
||||
public static <T extends Enum<T>> T checkEnum( int index, Class<T> klass, String value ) throws LuaException
|
||||
{
|
||||
for( T possibility : klass.getEnumConstants() )
|
||||
{
|
||||
if( possibility.name().equalsIgnoreCase( value ) ) return possibility;
|
||||
}
|
||||
|
||||
throw new LuaException( "bad argument #" + (index + 1) + " (unknown option " + value + ")" );
|
||||
}
|
||||
}
|
||||
170
src/main/java/dan200/computercraft/api/lua/MethodResult.java
Normal file
170
src/main/java/dan200/computercraft/api/lua/MethodResult.java
Normal file
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
|
||||
* For help using the API, and posting your mods, visit the forums at computercraft.info.
|
||||
*/
|
||||
package dan200.computercraft.api.lua;
|
||||
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* The result of invoking a Lua method.
|
||||
*
|
||||
* Method results either return a value immediately ({@link #of(Object...)} or yield control to the parent coroutine.
|
||||
* When the current coroutine is resumed, we invoke the provided {@link ILuaCallback#resume(Object[])} callback.
|
||||
*/
|
||||
public final class MethodResult
|
||||
{
|
||||
private static final MethodResult empty = new MethodResult( null, null );
|
||||
|
||||
private final Object[] result;
|
||||
private final ILuaCallback callback;
|
||||
private final int adjust;
|
||||
|
||||
private MethodResult( Object[] arguments, ILuaCallback callback )
|
||||
{
|
||||
this.result = arguments;
|
||||
this.callback = callback;
|
||||
this.adjust = 0;
|
||||
}
|
||||
|
||||
private MethodResult( Object[] arguments, ILuaCallback callback, int adjust )
|
||||
{
|
||||
this.result = arguments;
|
||||
this.callback = callback;
|
||||
this.adjust = adjust;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return no values immediately.
|
||||
*
|
||||
* @return A method result which returns immediately with no values.
|
||||
*/
|
||||
@Nonnull
|
||||
public static MethodResult of()
|
||||
{
|
||||
return empty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a single value immediately.
|
||||
*
|
||||
* Integers, doubles, floats, strings, booleans, {@link Map}, {@link Collection}s, arrays and {@code null} will be
|
||||
* converted to their corresponding Lua type. {@code byte[]} and {@link ByteBuffer} will be treated as binary
|
||||
* strings.
|
||||
*
|
||||
* In order to provide a custom object with methods, one may return a {@link IDynamicLuaObject}, or an arbitrary
|
||||
* class with {@link LuaFunction} annotations. Anything else will be converted to {@code nil}.
|
||||
*
|
||||
* @param value The value to return to the calling Lua function.
|
||||
* @return A method result which returns immediately with the given value.
|
||||
*/
|
||||
@Nonnull
|
||||
public static MethodResult of( @Nullable Object value )
|
||||
{
|
||||
return new MethodResult( new Object[] { value }, null );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return any number of values immediately.
|
||||
*
|
||||
* @param values The values to return. See {@link #of(Object)} for acceptable values.
|
||||
* @return A method result which returns immediately with the given values.
|
||||
*/
|
||||
@Nonnull
|
||||
public static MethodResult of( @Nullable Object... values )
|
||||
{
|
||||
return values == null || values.length == 0 ? empty : new MethodResult( values, null );
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for an event to occur on the computer, suspending the thread until it arises. This method is exactly
|
||||
* equivalent to {@code os.pullEvent()} in lua.
|
||||
*
|
||||
* @param filter A specific event to wait for, or null to wait for any event.
|
||||
* @param callback The callback to resume with the name of the event that occurred, and any event parameters.
|
||||
* @return The method result which represents this yield.
|
||||
* @see IComputerAccess#queueEvent(String, Object[])
|
||||
*/
|
||||
@Nonnull
|
||||
public static MethodResult pullEvent( @Nullable String filter, @Nonnull ILuaCallback callback )
|
||||
{
|
||||
Objects.requireNonNull( callback, "callback cannot be null" );
|
||||
return new MethodResult( new Object[] { filter }, results -> {
|
||||
if( results.length >= 1 && results[0].equals( "terminate" ) ) throw new LuaException( "Terminated", 0 );
|
||||
return callback.resume( results );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* The same as {@link #pullEvent(String, ILuaCallback)}, except "terminated" events are ignored. Only use this if
|
||||
* you want to prevent program termination, which is not recommended. This method is exactly equivalent to
|
||||
* {@code os.pullEventRaw()} in Lua.
|
||||
*
|
||||
* @param filter A specific event to wait for, or null to wait for any event.
|
||||
* @param callback The callback to resume with the name of the event that occurred, and any event parameters.
|
||||
* @return The method result which represents this yield.
|
||||
* @see #pullEvent(String, ILuaCallback)
|
||||
*/
|
||||
@Nonnull
|
||||
public static MethodResult pullEventRaw( @Nullable String filter, @Nonnull ILuaCallback callback )
|
||||
{
|
||||
Objects.requireNonNull( callback, "callback cannot be null" );
|
||||
return new MethodResult( new Object[] { filter }, callback );
|
||||
}
|
||||
|
||||
/**
|
||||
* Yield the current coroutine with some arguments until it is resumed. This method is exactly equivalent to
|
||||
* {@code coroutine.yield()} in lua. Use {@code pullEvent()} if you wish to wait for events.
|
||||
*
|
||||
* @param arguments An object array containing the arguments to pass to coroutine.yield()
|
||||
* @param callback The callback to resume with an array containing the return values from coroutine.yield()
|
||||
* @return The method result which represents this yield.
|
||||
* @see #pullEvent(String, ILuaCallback)
|
||||
*/
|
||||
@Nonnull
|
||||
public static MethodResult yield( @Nullable Object[] arguments, @Nonnull ILuaCallback callback )
|
||||
{
|
||||
Objects.requireNonNull( callback, "callback cannot be null" );
|
||||
return new MethodResult( arguments, callback );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Object[] getResult()
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ILuaCallback getCallback()
|
||||
{
|
||||
return callback;
|
||||
}
|
||||
|
||||
public int getErrorAdjust()
|
||||
{
|
||||
return adjust;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase the Lua error by a specific amount. One should never need to use this function - it largely exists for
|
||||
* some CC internal code.
|
||||
*
|
||||
* @param adjust The amount to increase the level by.
|
||||
* @return The new {@link MethodResult} with an adjusted error. This has no effect on immediate results.
|
||||
*/
|
||||
@Nonnull
|
||||
public MethodResult adjustError( int adjust )
|
||||
{
|
||||
if( adjust < 0 ) throw new IllegalArgumentException( "cannot adjust by a negative amount" );
|
||||
if( adjust == 0 || callback == null ) return this;
|
||||
return new MethodResult( result, callback, this.adjust + adjust );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
|
||||
* For help using the API, and posting your mods, visit the forums at computercraft.info.
|
||||
*/
|
||||
package dan200.computercraft.api.lua;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* An implementation of {@link IArguments} which wraps an array of {@link Object}.
|
||||
*/
|
||||
public final class ObjectArguments implements IArguments
|
||||
{
|
||||
private static final IArguments EMPTY = new ObjectArguments();
|
||||
private final List<Object> args;
|
||||
|
||||
@Deprecated
|
||||
@SuppressWarnings( "unused" )
|
||||
public ObjectArguments( IArguments arguments )
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
public ObjectArguments( Object... args )
|
||||
{
|
||||
this.args = Arrays.asList( args );
|
||||
}
|
||||
|
||||
public ObjectArguments( List<Object> args )
|
||||
{
|
||||
this.args = Objects.requireNonNull( args );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count()
|
||||
{
|
||||
return args.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IArguments drop( int count )
|
||||
{
|
||||
if( count < 0 ) throw new IllegalStateException( "count cannot be negative" );
|
||||
if( count == 0 ) return this;
|
||||
if( count >= args.size() ) return EMPTY;
|
||||
|
||||
return new ObjectArguments( args.subList( count, args.size() ) );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object get( int index )
|
||||
{
|
||||
return index >= args.size() ? null : args.get( index );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getAll()
|
||||
{
|
||||
return args.toArray();
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,10 @@ package dan200.computercraft.api.peripheral;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.filesystem.IMount;
|
||||
import dan200.computercraft.api.filesystem.IWritableMount;
|
||||
import dan200.computercraft.api.lua.ILuaCallback;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.ILuaTask;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
@@ -146,9 +148,9 @@ public interface IComputerAccess
|
||||
*
|
||||
* You may supply {@code null} to indicate that no arguments are to be supplied.
|
||||
* @throws NotAttachedException If the peripheral has been detached.
|
||||
* @see IPeripheral#callMethod
|
||||
* @see MethodResult#pullEvent(String, ILuaCallback)
|
||||
*/
|
||||
void queueEvent( @Nonnull String event, @Nullable Object[] arguments );
|
||||
void queueEvent( @Nonnull String event, @Nullable Object... arguments );
|
||||
|
||||
/**
|
||||
* Get a string, unique to the computer, by which the computer refers to this peripheral.
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
|
||||
* For help using the API, and posting your mods, visit the forums at computercraft.info.
|
||||
*/
|
||||
package dan200.computercraft.api.peripheral;
|
||||
|
||||
import dan200.computercraft.api.lua.*;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* A peripheral whose methods are not known at runtime.
|
||||
*
|
||||
* This behaves similarly to {@link IDynamicLuaObject}, though also accepting the current {@link IComputerAccess}.
|
||||
* Generally one may use {@link LuaFunction} instead of implementing this interface.
|
||||
*/
|
||||
public interface IDynamicPeripheral extends IPeripheral
|
||||
{
|
||||
/**
|
||||
* Should return an array of strings that identify the methods that this peripheral exposes to Lua. This will be
|
||||
* called once before each attachment, and should not change when called multiple times.
|
||||
*
|
||||
* @return An array of strings representing method names.
|
||||
* @see #callMethod
|
||||
*/
|
||||
@Nonnull
|
||||
String[] getMethodNames();
|
||||
|
||||
/**
|
||||
* This is called when a lua program on an attached computer calls {@code peripheral.call()} with
|
||||
* one of the methods exposed by {@link #getMethodNames()}.
|
||||
*
|
||||
* Be aware that this will be called from the ComputerCraft Lua thread, and must be thread-safe when interacting
|
||||
* with Minecraft objects.
|
||||
*
|
||||
* @param computer The interface to the computer that is making the call. Remember that multiple
|
||||
* computers can be attached to a peripheral at once.
|
||||
* @param context The context of the currently running lua thread. This can be used to wait for events
|
||||
* or otherwise yield.
|
||||
* @param method An integer identifying which of the methods from getMethodNames() the computercraft
|
||||
* wishes to call. The integer indicates the index into the getMethodNames() table
|
||||
* that corresponds to the string passed into peripheral.call()
|
||||
* @param arguments The arguments for this method.
|
||||
* @return A {@link MethodResult} containing the values to return or the action to perform.
|
||||
* @throws LuaException If you throw any exception from this function, a lua error will be raised with the
|
||||
* same message as your exception. Use this to throw appropriate errors if the wrong
|
||||
* arguments are supplied to your method.
|
||||
* @see #getMethodNames()
|
||||
*/
|
||||
@Nonnull
|
||||
MethodResult callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull IArguments arguments ) throws LuaException;
|
||||
}
|
||||
@@ -5,15 +5,21 @@
|
||||
*/
|
||||
package dan200.computercraft.api.peripheral;
|
||||
|
||||
import dan200.computercraft.api.lua.ArgumentHelper;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The interface that defines a peripheral. See {@link IPeripheralProvider} for how to associate blocks with peripherals.
|
||||
* The interface that defines a peripheral.
|
||||
*
|
||||
* In order to expose a peripheral for your block or tile entity, you may either attach a {@link Capability}, or
|
||||
* register a {@link IPeripheralProvider}. It is <em>not</em> recommended to implement {@link IPeripheral} directly on
|
||||
* the tile.
|
||||
*
|
||||
* Peripherals should provide a series of methods to the user, either using {@link LuaFunction} or by implementing
|
||||
* {@link IDynamicPeripheral}.
|
||||
*/
|
||||
public interface IPeripheral
|
||||
{
|
||||
@@ -26,57 +32,6 @@ public interface IPeripheral
|
||||
@Nonnull
|
||||
String getType();
|
||||
|
||||
/**
|
||||
* Should return an array of strings that identify the methods that this
|
||||
* peripheral exposes to Lua. This will be called once before each attachment,
|
||||
* and should not change when called multiple times.
|
||||
*
|
||||
* @return An array of strings representing method names.
|
||||
* @see #callMethod
|
||||
*/
|
||||
@Nonnull
|
||||
String[] getMethodNames();
|
||||
|
||||
/**
|
||||
* This is called when a lua program on an attached computer calls {@code peripheral.call()} with
|
||||
* one of the methods exposed by {@link #getMethodNames()}.
|
||||
*
|
||||
* Be aware that this will be called from the ComputerCraft Lua thread, and must be thread-safe when interacting
|
||||
* with Minecraft objects.
|
||||
*
|
||||
* @param computer The interface to the computer that is making the call. Remember that multiple
|
||||
* computers can be attached to a peripheral at once.
|
||||
* @param context The context of the currently running lua thread. This can be used to wait for events
|
||||
* or otherwise yield.
|
||||
* @param method An integer identifying which of the methods from getMethodNames() the computercraft
|
||||
* wishes to call. The integer indicates the index into the getMethodNames() table
|
||||
* that corresponds to the string passed into peripheral.call()
|
||||
* @param arguments An array of objects, representing the arguments passed into {@code peripheral.call()}.<br>
|
||||
* Lua values of type "string" will be represented by Object type String.<br>
|
||||
* Lua values of type "number" will be represented by Object type Double.<br>
|
||||
* Lua values of type "boolean" will be represented by Object type Boolean.<br>
|
||||
* Lua values of type "table" will be represented by Object type Map.<br>
|
||||
* Lua values of any other type will be represented by a null object.<br>
|
||||
* This array will be empty if no arguments are passed.
|
||||
*
|
||||
* It is recommended you use {@link ArgumentHelper} in order to validate and process arguments.
|
||||
* @return An array of objects, representing values you wish to return to the lua program. Integers, Doubles, Floats,
|
||||
* Strings, Booleans, Maps, ILuaObject and null be converted to their corresponding lua type. All other types will
|
||||
* be converted to nil.
|
||||
*
|
||||
* You may return null to indicate no values should be returned.
|
||||
* @throws LuaException If you throw any exception from this function, a lua error will be raised with the
|
||||
* same message as your exception. Use this to throw appropriate errors if the wrong
|
||||
* arguments are supplied to your method.
|
||||
* @throws InterruptedException If the user shuts down or reboots the computer the coroutine is suspended,
|
||||
* InterruptedException will be thrown. This exception must not be caught or
|
||||
* intercepted, or the computer will leak memory and end up in a broken state.
|
||||
* @see #getMethodNames
|
||||
* @see ArgumentHelper
|
||||
*/
|
||||
@Nullable
|
||||
Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException;
|
||||
|
||||
/**
|
||||
* Is called when when a computer is attaching to the peripheral.
|
||||
*
|
||||
@@ -126,10 +81,10 @@ public interface IPeripheral
|
||||
*
|
||||
* @return The object this peripheral targets
|
||||
*/
|
||||
@Nonnull
|
||||
@Nullable
|
||||
default Object getTarget()
|
||||
{
|
||||
return this;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,14 +9,15 @@ import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.Direction;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* This interface is used to create peripheral implementations for blocks.
|
||||
*
|
||||
* If you have a {@link TileEntity} which acts as a peripheral, you may alternatively implement {@link IPeripheralTile}.
|
||||
* If you have a {@link TileEntity} which acts as a peripheral, you may alternatively expose the {@link IPeripheral}
|
||||
* capability.
|
||||
*
|
||||
* @see dan200.computercraft.api.ComputerCraftAPI#registerPeripheralProvider(IPeripheralProvider)
|
||||
*/
|
||||
@@ -29,9 +30,9 @@ public interface IPeripheralProvider
|
||||
* @param world The world the block is in.
|
||||
* @param pos The position the block is at.
|
||||
* @param side The side to get the peripheral from.
|
||||
* @return A peripheral, or {@code null} if there is not a peripheral here you'd like to handle.
|
||||
* @return A peripheral, or {@link LazyOptional#empty()} if there is not a peripheral here you'd like to handle.
|
||||
* @see dan200.computercraft.api.ComputerCraftAPI#registerPeripheralProvider(IPeripheralProvider)
|
||||
*/
|
||||
@Nullable
|
||||
IPeripheral getPeripheral( @Nonnull World world, @Nonnull BlockPos pos, @Nonnull Direction side );
|
||||
@Nonnull
|
||||
LazyOptional<IPeripheral> getPeripheral( @Nonnull World world, @Nonnull BlockPos pos, @Nonnull Direction side );
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
|
||||
* For help using the API, and posting your mods, visit the forums at computercraft.info.
|
||||
*/
|
||||
package dan200.computercraft.api.peripheral;
|
||||
|
||||
import net.minecraft.util.Direction;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A {@link net.minecraft.tileentity.TileEntity} which may act as a peripheral.
|
||||
*
|
||||
* If you need more complex capabilities (such as handling TEs not belonging to your mod), you should use
|
||||
* {@link IPeripheralProvider}.
|
||||
*/
|
||||
public interface IPeripheralTile
|
||||
{
|
||||
/**
|
||||
* Get the peripheral on the given {@code side}.
|
||||
*
|
||||
* @param side The side to get the peripheral from.
|
||||
* @return A peripheral, or {@code null} if there is not a peripheral here.
|
||||
* @see IPeripheralProvider#getPeripheral(World, BlockPos, Direction)
|
||||
*/
|
||||
@Nullable
|
||||
IPeripheral getPeripheral( @Nonnull Direction side );
|
||||
}
|
||||
@@ -15,7 +15,7 @@ public class NotAttachedException extends IllegalStateException
|
||||
|
||||
public NotAttachedException()
|
||||
{
|
||||
super( "You are not attached to this Computer" );
|
||||
super( "You are not attached to this computer" );
|
||||
}
|
||||
|
||||
public NotAttachedException( String s )
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
package dan200.computercraft.api.turtle;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.ILuaCallback;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import net.minecraft.inventory.IInventory;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
@@ -228,21 +228,15 @@ public interface ITurtleAccess
|
||||
* be supplied as a parameter to a "turtle_response" event issued to the turtle after the command has completed. Look at the
|
||||
* lua source code for "rom/apis/turtle" for how to build a lua wrapper around this functionality.
|
||||
*
|
||||
* @param context The Lua context to pull events from.
|
||||
* @param command An object which will execute the custom command when its point in the queue is reached
|
||||
* @return The objects the command returned when executed. you should probably return these to the player
|
||||
* unchanged if called from a peripheral method.
|
||||
* @throws UnsupportedOperationException When attempting to execute a command on the client side.
|
||||
* @throws LuaException If the user presses CTRL+T to terminate the current program while {@code executeCommand()} is
|
||||
* waiting for an event, a "Terminated" exception will be thrown here.
|
||||
* @throws InterruptedException If the user shuts down or reboots the computer while pullEvent() is waiting for an
|
||||
* event, InterruptedException will be thrown. This exception must not be caught or
|
||||
* intercepted, or the computer will leak memory and end up in a broken state.
|
||||
* @see ITurtleCommand
|
||||
* @see ILuaContext#pullEvent(String)
|
||||
* @see MethodResult#pullEvent(String, ILuaCallback)
|
||||
*/
|
||||
@Nonnull
|
||||
Object[] executeCommand( @Nonnull ILuaContext context, @Nonnull ITurtleCommand command ) throws LuaException, InterruptedException;
|
||||
MethodResult executeCommand( @Nonnull ITurtleCommand command );
|
||||
|
||||
/**
|
||||
* Start playing a specific animation. This will prevent other turtle commands from executing until
|
||||
|
||||
@@ -5,14 +5,12 @@
|
||||
*/
|
||||
package dan200.computercraft.api.turtle;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* An interface for objects executing custom turtle commands, used with {@link ITurtleAccess#executeCommand(ILuaContext, ITurtleCommand)}.
|
||||
* An interface for objects executing custom turtle commands, used with {@link ITurtleAccess#executeCommand(ITurtleCommand)}.
|
||||
*
|
||||
* @see ITurtleAccess#executeCommand(ILuaContext, ITurtleCommand)
|
||||
* @see ITurtleAccess#executeCommand(ITurtleCommand)
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ITurtleCommand
|
||||
@@ -25,7 +23,7 @@ public interface ITurtleCommand
|
||||
*
|
||||
* @param turtle Access to the turtle for whom the command was issued.
|
||||
* @return A result, indicating whether this action succeeded or not.
|
||||
* @see ITurtleAccess#executeCommand(ILuaContext, ITurtleCommand)
|
||||
* @see ITurtleAccess#executeCommand(ITurtleCommand)
|
||||
* @see TurtleCommandResult#success()
|
||||
* @see TurtleCommandResult#failure(String)
|
||||
* @see TurtleCommandResult
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
*/
|
||||
package dan200.computercraft.api.turtle.event;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
@@ -223,7 +222,7 @@ public abstract class TurtleBlockEvent extends TurtlePlayerEvent
|
||||
* Add new information to the inspection result. Note this will override fields with the same name.
|
||||
*
|
||||
* @param newData The data to add. Note all values should be convertible to Lua (see
|
||||
* {@link dan200.computercraft.api.peripheral.IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}).
|
||||
* {@link MethodResult#of(Object)}).
|
||||
*/
|
||||
public void addData( @Nonnull Map<String, ?> newData )
|
||||
{
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
*/
|
||||
package dan200.computercraft.api.turtle.event;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
@@ -63,7 +62,7 @@ public class TurtleInspectItemEvent extends TurtleActionEvent
|
||||
* Add new information to the inspection result. Note this will override fields with the same name.
|
||||
*
|
||||
* @param newData The data to add. Note all values should be convertible to Lua (see
|
||||
* {@link dan200.computercraft.api.peripheral.IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}).
|
||||
* {@link MethodResult#of(Object)}).
|
||||
*/
|
||||
public void addData( @Nonnull Map<String, ?> newData )
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.client.render.TurtleModelLoader;
|
||||
import dan200.computercraft.shared.common.IColouredItem;
|
||||
import dan200.computercraft.shared.media.items.ItemDisk;
|
||||
import dan200.computercraft.shared.media.items.ItemTreasureDisk;
|
||||
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
|
||||
import dan200.computercraft.shared.util.Colour;
|
||||
import net.minecraft.client.renderer.model.IBakedModel;
|
||||
@@ -119,6 +120,11 @@ public final class ClientRegistry
|
||||
ComputerCraft.Items.disk
|
||||
);
|
||||
|
||||
event.getItemColors().register(
|
||||
( stack, layer ) -> layer == 1 ? ItemTreasureDisk.getColour( stack ) : 0xFFFFFF,
|
||||
ComputerCraft.Items.treasureDisk
|
||||
);
|
||||
|
||||
event.getItemColors().register( ( stack, layer ) -> {
|
||||
switch( layer )
|
||||
{
|
||||
|
||||
@@ -41,15 +41,14 @@ public final class FixedWidthFontRenderer
|
||||
{
|
||||
}
|
||||
|
||||
private static float toGreyscale( double[] rgb )
|
||||
public static float toGreyscale( double[] rgb )
|
||||
{
|
||||
return (float) ((rgb[0] + rgb[1] + rgb[2]) / 3);
|
||||
}
|
||||
|
||||
private static int getColour( char c )
|
||||
public static int getColour( char c, Colour def )
|
||||
{
|
||||
int i = "0123456789abcdef".indexOf( c );
|
||||
return i < 0 ? 0 : 15 - i;
|
||||
return 15 - Terminal.getColour( c, def );
|
||||
}
|
||||
|
||||
private static void drawChar( Matrix4f transform, IVertexBuilder buffer, float x, float y, int index, float r, float g, float b )
|
||||
@@ -83,7 +82,7 @@ public final class FixedWidthFontRenderer
|
||||
|
||||
private static void drawQuad( Matrix4f transform, IVertexBuilder buffer, float x, float y, float width, float height, Palette palette, boolean greyscale, char colourIndex )
|
||||
{
|
||||
double[] colour = palette.getColour( getColour( colourIndex ) );
|
||||
double[] colour = palette.getColour( getColour( colourIndex, Colour.BLACK ) );
|
||||
float r, g, b;
|
||||
if( greyscale )
|
||||
{
|
||||
@@ -151,7 +150,7 @@ public final class FixedWidthFontRenderer
|
||||
|
||||
for( int i = 0; i < text.length(); i++ )
|
||||
{
|
||||
double[] colour = palette.getColour( 15 - "0123456789abcdef".indexOf( textColour.charAt( i ) ) );
|
||||
double[] colour = palette.getColour( getColour( textColour.charAt( i ), Colour.BLACK ) );
|
||||
float r, g, b;
|
||||
if( greyscale )
|
||||
{
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.client.render;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
|
||||
import dan200.computercraft.shared.util.Palette;
|
||||
import net.minecraft.client.renderer.Matrix4f;
|
||||
import net.minecraft.client.renderer.texture.TextureUtil;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
class MonitorTextureBufferShader
|
||||
{
|
||||
static final int TEXTURE_INDEX = GL13.GL_TEXTURE3;
|
||||
|
||||
private static final FloatBuffer MATRIX_BUFFER = BufferUtils.createFloatBuffer( 16 );
|
||||
private static final FloatBuffer PALETTE_BUFFER = BufferUtils.createFloatBuffer( 16 * 3 );
|
||||
|
||||
private static int uniformMv;
|
||||
private static int uniformP;
|
||||
|
||||
private static int uniformFont;
|
||||
private static int uniformWidth;
|
||||
private static int uniformHeight;
|
||||
private static int uniformTbo;
|
||||
private static int uniformPalette;
|
||||
|
||||
private static boolean initialised;
|
||||
private static boolean ok;
|
||||
private static int program;
|
||||
|
||||
static void setupUniform( Matrix4f transform, int width, int height, Palette palette, boolean greyscale )
|
||||
{
|
||||
MATRIX_BUFFER.rewind();
|
||||
transform.write( MATRIX_BUFFER );
|
||||
MATRIX_BUFFER.rewind();
|
||||
RenderSystem.glUniformMatrix4( uniformMv, false, MATRIX_BUFFER );
|
||||
|
||||
// TODO: Cache this?
|
||||
MATRIX_BUFFER.rewind();
|
||||
GL11.glGetFloatv( GL11.GL_PROJECTION_MATRIX, MATRIX_BUFFER );
|
||||
MATRIX_BUFFER.rewind();
|
||||
RenderSystem.glUniformMatrix4( uniformP, false, MATRIX_BUFFER );
|
||||
|
||||
RenderSystem.glUniform1i( uniformWidth, width );
|
||||
RenderSystem.glUniform1i( uniformHeight, height );
|
||||
|
||||
// TODO: Cache this? Maybe??
|
||||
PALETTE_BUFFER.rewind();
|
||||
for( int i = 0; i < 16; i++ )
|
||||
{
|
||||
double[] colour = palette.getColour( i );
|
||||
if( greyscale )
|
||||
{
|
||||
float f = FixedWidthFontRenderer.toGreyscale( colour );
|
||||
PALETTE_BUFFER.put( f ).put( f ).put( f );
|
||||
}
|
||||
else
|
||||
{
|
||||
PALETTE_BUFFER.put( (float) colour[0] ).put( (float) colour[1] ).put( (float) colour[2] );
|
||||
}
|
||||
}
|
||||
PALETTE_BUFFER.flip();
|
||||
RenderSystem.glUniform3( uniformPalette, PALETTE_BUFFER );
|
||||
}
|
||||
|
||||
static boolean use()
|
||||
{
|
||||
if( initialised )
|
||||
{
|
||||
if( ok ) GlStateManager.useProgram( program );
|
||||
return ok;
|
||||
}
|
||||
|
||||
if( ok = load() )
|
||||
{
|
||||
GL20.glUseProgram( program );
|
||||
RenderSystem.glUniform1i( uniformFont, 0 );
|
||||
RenderSystem.glUniform1i( uniformTbo, TEXTURE_INDEX - GL13.GL_TEXTURE0 );
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
private static boolean load()
|
||||
{
|
||||
initialised = true;
|
||||
|
||||
try
|
||||
{
|
||||
int vertexShader = loadShader( GL20.GL_VERTEX_SHADER, "assets/computercraft/shaders/monitor.vert" );
|
||||
int fragmentShader = loadShader( GL20.GL_FRAGMENT_SHADER, "assets/computercraft/shaders/monitor.frag" );
|
||||
|
||||
program = GlStateManager.createProgram();
|
||||
GlStateManager.attachShader( program, vertexShader );
|
||||
GlStateManager.attachShader( program, fragmentShader );
|
||||
GL20.glBindAttribLocation( program, 0, "v_pos" );
|
||||
|
||||
GlStateManager.linkProgram( program );
|
||||
boolean ok = GlStateManager.getProgram( program, GL20.GL_LINK_STATUS ) != 0;
|
||||
String log = GlStateManager.getProgramInfoLog( program, Short.MAX_VALUE ).trim();
|
||||
if( !Strings.isNullOrEmpty( log ) )
|
||||
{
|
||||
ComputerCraft.log.warn( "Problems when linking monitor shader: {}", log );
|
||||
}
|
||||
|
||||
GL20.glDetachShader( program, vertexShader );
|
||||
GL20.glDetachShader( program, fragmentShader );
|
||||
GlStateManager.deleteShader( vertexShader );
|
||||
GlStateManager.deleteShader( fragmentShader );
|
||||
|
||||
if( !ok ) return false;
|
||||
|
||||
uniformMv = getUniformLocation( program, "u_mv" );
|
||||
uniformP = getUniformLocation( program, "u_p" );
|
||||
|
||||
uniformFont = getUniformLocation( program, "u_font" );
|
||||
uniformWidth = getUniformLocation( program, "u_width" );
|
||||
uniformHeight = getUniformLocation( program, "u_height" );
|
||||
uniformTbo = getUniformLocation( program, "u_tbo" );
|
||||
uniformPalette = getUniformLocation( program, "u_palette" );
|
||||
|
||||
ComputerCraft.log.info( "Loaded monitor shader." );
|
||||
return true;
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
ComputerCraft.log.error( "Cannot load monitor shaders", e );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static int loadShader( int kind, String path )
|
||||
{
|
||||
InputStream stream = TileEntityMonitorRenderer.class.getClassLoader().getResourceAsStream( path );
|
||||
if( stream == null ) throw new IllegalArgumentException( "Cannot find " + path );
|
||||
String contents = TextureUtil.readResourceAsString( stream );
|
||||
|
||||
int shader = GlStateManager.createShader( kind );
|
||||
|
||||
GlStateManager.shaderSource( shader, contents );
|
||||
GlStateManager.compileShader( shader );
|
||||
|
||||
boolean ok = GlStateManager.getShader( shader, GL20.GL_COMPILE_STATUS ) != 0;
|
||||
String log = GlStateManager.getShaderInfoLog( shader, Short.MAX_VALUE ).trim();
|
||||
if( !Strings.isNullOrEmpty( log ) )
|
||||
{
|
||||
ComputerCraft.log.warn( "Problems when loading monitor shader {}: {}", path, log );
|
||||
}
|
||||
|
||||
if( !ok ) throw new IllegalStateException( "Cannot compile shader " + path );
|
||||
return shader;
|
||||
}
|
||||
|
||||
private static int getUniformLocation( int program, String name )
|
||||
{
|
||||
int uniform = GlStateManager.getUniformLocation( program, name );
|
||||
if( uniform == -1 ) throw new IllegalStateException( "Cannot find uniform " + name );
|
||||
return uniform;
|
||||
}
|
||||
}
|
||||
@@ -6,22 +6,33 @@
|
||||
package dan200.computercraft.client.render;
|
||||
|
||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import com.mojang.blaze3d.vertex.IVertexBuilder;
|
||||
import dan200.computercraft.client.FrameInfo;
|
||||
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.terminal.TextBuffer;
|
||||
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
|
||||
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
|
||||
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
|
||||
import dan200.computercraft.shared.util.Colour;
|
||||
import dan200.computercraft.shared.util.DirectionUtil;
|
||||
import net.minecraft.client.renderer.*;
|
||||
import net.minecraft.client.renderer.tileentity.TileEntityRenderer;
|
||||
import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
|
||||
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
|
||||
import net.minecraft.client.renderer.vertex.VertexBuffer;
|
||||
import net.minecraft.util.Direction;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.*;
|
||||
|
||||
public class TileEntityMonitorRenderer extends TileEntityRenderer<TileMonitor>
|
||||
{
|
||||
@@ -90,50 +101,23 @@ public class TileEntityMonitorRenderer extends TileEntityRenderer<TileMonitor>
|
||||
Terminal terminal = originTerminal.getTerminal();
|
||||
if( terminal != null )
|
||||
{
|
||||
boolean redraw = originTerminal.pollTerminalChanged();
|
||||
if( originTerminal.buffer == null )
|
||||
{
|
||||
originTerminal.createBuffer( MonitorRenderer.VBO );
|
||||
redraw = true;
|
||||
}
|
||||
VertexBuffer vbo = originTerminal.buffer;
|
||||
|
||||
// Draw a terminal
|
||||
double xScale = xSize / (terminal.getWidth() * FixedWidthFontRenderer.FONT_WIDTH);
|
||||
double yScale = ySize / (terminal.getHeight() * FixedWidthFontRenderer.FONT_HEIGHT);
|
||||
int width = terminal.getWidth(), height = terminal.getHeight();
|
||||
int pixelWidth = width * FONT_WIDTH, pixelHeight = height * FONT_HEIGHT;
|
||||
double xScale = xSize / pixelWidth;
|
||||
double yScale = ySize / pixelHeight;
|
||||
transform.push();
|
||||
transform.scale( (float) xScale, (float) -yScale, 1.0f );
|
||||
|
||||
float xMargin = (float) (MARGIN / xScale);
|
||||
float yMargin = (float) (MARGIN / yScale);
|
||||
|
||||
Matrix4f matrix = transform.getLast().getMatrix();
|
||||
|
||||
if( redraw )
|
||||
{
|
||||
Tessellator tessellator = Tessellator.getInstance();
|
||||
BufferBuilder builder = tessellator.getBuffer();
|
||||
builder.begin( FixedWidthFontRenderer.TYPE.getDrawMode(), FixedWidthFontRenderer.TYPE.getVertexFormat() );
|
||||
FixedWidthFontRenderer.drawTerminalWithoutCursor(
|
||||
IDENTITY, builder, 0, 0,
|
||||
terminal, !originTerminal.isColour(), yMargin, yMargin, xMargin, xMargin
|
||||
);
|
||||
|
||||
builder.finishDrawing();
|
||||
vbo.upload( builder );
|
||||
}
|
||||
|
||||
// Sneaky hack here: we get a buffer now in order to flush existing ones and set up the appropriate
|
||||
// render state. I've no clue how well this'll work in future versions of Minecraft, but it does the trick
|
||||
// for now.
|
||||
IVertexBuilder buffer = renderer.getBuffer( FixedWidthFontRenderer.TYPE );
|
||||
FixedWidthFontRenderer.TYPE.setupRenderState();
|
||||
|
||||
vbo.bindBuffer();
|
||||
FixedWidthFontRenderer.TYPE.getVertexFormat().setupBufferState( 0L );
|
||||
vbo.draw( matrix, FixedWidthFontRenderer.TYPE.getDrawMode() );
|
||||
VertexBuffer.unbindBuffer();
|
||||
FixedWidthFontRenderer.TYPE.getVertexFormat().clearBufferState();
|
||||
renderTerminal( matrix, originTerminal, (float) (MARGIN / xScale), (float) (MARGIN / yScale) );
|
||||
|
||||
// We don't draw the cursor with the VBO, as it's dynamic and so we'll end up refreshing far more than is
|
||||
// reasonable.
|
||||
@@ -158,4 +142,88 @@ public class TileEntityMonitorRenderer extends TileEntityRenderer<TileMonitor>
|
||||
|
||||
transform.pop();
|
||||
}
|
||||
|
||||
private static void renderTerminal( Matrix4f matrix, ClientMonitor monitor, float xMargin, float yMargin )
|
||||
{
|
||||
Terminal terminal = monitor.getTerminal();
|
||||
|
||||
MonitorRenderer renderType = MonitorRenderer.current();
|
||||
boolean redraw = monitor.pollTerminalChanged();
|
||||
if( monitor.createBuffer( renderType ) ) redraw = true;
|
||||
|
||||
switch( renderType )
|
||||
{
|
||||
case VBO:
|
||||
{
|
||||
VertexBuffer vbo = monitor.buffer;
|
||||
if( redraw )
|
||||
{
|
||||
Tessellator tessellator = Tessellator.getInstance();
|
||||
BufferBuilder builder = tessellator.getBuffer();
|
||||
builder.begin( FixedWidthFontRenderer.TYPE.getDrawMode(), FixedWidthFontRenderer.TYPE.getVertexFormat() );
|
||||
FixedWidthFontRenderer.drawTerminalWithoutCursor(
|
||||
IDENTITY, builder, 0, 0,
|
||||
terminal, !monitor.isColour(), yMargin, yMargin, xMargin, xMargin
|
||||
);
|
||||
|
||||
builder.finishDrawing();
|
||||
vbo.upload( builder );
|
||||
}
|
||||
|
||||
vbo.bindBuffer();
|
||||
FixedWidthFontRenderer.TYPE.getVertexFormat().setupBufferState( 0L );
|
||||
vbo.draw( matrix, FixedWidthFontRenderer.TYPE.getDrawMode() );
|
||||
VertexBuffer.unbindBuffer();
|
||||
FixedWidthFontRenderer.TYPE.getVertexFormat().clearBufferState();
|
||||
break;
|
||||
}
|
||||
|
||||
case TBO:
|
||||
{
|
||||
if( !MonitorTextureBufferShader.use() ) return;
|
||||
|
||||
int width = terminal.getWidth(), height = terminal.getHeight();
|
||||
int pixelWidth = width * FONT_WIDTH, pixelHeight = height * FONT_HEIGHT;
|
||||
|
||||
if( redraw )
|
||||
{
|
||||
ByteBuffer buffer = GLAllocation.createDirectByteBuffer( width * height * 3 );
|
||||
for( int y = 0; y < height; y++ )
|
||||
{
|
||||
TextBuffer text = terminal.getLine( y ), textColour = terminal.getTextColourLine( y ), background = terminal.getBackgroundColourLine( y );
|
||||
for( int x = 0; x < width; x++ )
|
||||
{
|
||||
buffer.put( (byte) (text.charAt( x ) & 0xFF) );
|
||||
buffer.put( (byte) getColour( textColour.charAt( x ), Colour.WHITE ) );
|
||||
buffer.put( (byte) getColour( background.charAt( x ), Colour.BLACK ) );
|
||||
}
|
||||
}
|
||||
buffer.flip();
|
||||
|
||||
GlStateManager.bindBuffer( GL31.GL_TEXTURE_BUFFER, monitor.tboBuffer );
|
||||
GlStateManager.bufferData( GL31.GL_TEXTURE_BUFFER, buffer, GL20.GL_STATIC_DRAW );
|
||||
GlStateManager.bindBuffer( GL31.GL_TEXTURE_BUFFER, 0 );
|
||||
}
|
||||
|
||||
// Nobody knows what they're doing!
|
||||
GlStateManager.activeTexture( MonitorTextureBufferShader.TEXTURE_INDEX );
|
||||
GL11.glBindTexture( GL31.GL_TEXTURE_BUFFER, monitor.tboTexture );
|
||||
GlStateManager.activeTexture( GL13.GL_TEXTURE0 );
|
||||
|
||||
MonitorTextureBufferShader.setupUniform( matrix, width, height, terminal.getPalette(), !monitor.isColour() );
|
||||
|
||||
Tessellator tessellator = Tessellator.getInstance();
|
||||
BufferBuilder buffer = tessellator.getBuffer();
|
||||
buffer.begin( GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION );
|
||||
buffer.pos( -xMargin, -yMargin, 0 ).endVertex();
|
||||
buffer.pos( -xMargin, pixelHeight + yMargin, 0 ).endVertex();
|
||||
buffer.pos( pixelWidth + xMargin, -yMargin, 0 ).endVertex();
|
||||
buffer.pos( pixelWidth + xMargin, pixelHeight + yMargin, 0 ).endVertex();
|
||||
tessellator.draw();
|
||||
|
||||
GlStateManager.useProgram( 0 );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ public abstract class ComputerAccess implements IComputerAccess
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queueEvent( @Nonnull final String event, final Object[] arguments )
|
||||
public void queueEvent( @Nonnull String event, Object... arguments )
|
||||
{
|
||||
Objects.requireNonNull( event, "event cannot be null" );
|
||||
m_environment.queueEvent( event, arguments );
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
|
||||
import dan200.computercraft.core.apis.handles.BinaryWritableHandle;
|
||||
import dan200.computercraft.core.apis.handles.EncodedReadableHandle;
|
||||
@@ -17,7 +17,6 @@ import dan200.computercraft.core.filesystem.FileSystemException;
|
||||
import dan200.computercraft.core.filesystem.FileSystemWrapper;
|
||||
import dan200.computercraft.core.tracking.TrackingField;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
@@ -29,17 +28,14 @@ import java.util.Map;
|
||||
import java.util.OptionalLong;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static dan200.computercraft.api.lua.ArgumentHelper.getString;
|
||||
|
||||
public class FSAPI implements ILuaAPI
|
||||
{
|
||||
private IAPIEnvironment m_env;
|
||||
private FileSystem m_fileSystem;
|
||||
private final IAPIEnvironment environment;
|
||||
private FileSystem fileSystem = null;
|
||||
|
||||
public FSAPI( IAPIEnvironment env )
|
||||
{
|
||||
m_env = env;
|
||||
m_fileSystem = null;
|
||||
environment = env;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -51,329 +47,280 @@ public class FSAPI implements ILuaAPI
|
||||
@Override
|
||||
public void startup()
|
||||
{
|
||||
m_fileSystem = m_env.getFileSystem();
|
||||
fileSystem = environment.getFileSystem();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown()
|
||||
{
|
||||
m_fileSystem = null;
|
||||
fileSystem = null;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
@LuaFunction
|
||||
public final String[] list( String path ) throws LuaException
|
||||
{
|
||||
return new String[] {
|
||||
"list",
|
||||
"combine",
|
||||
"getName",
|
||||
"getSize",
|
||||
"exists",
|
||||
"isDir",
|
||||
"isReadOnly",
|
||||
"makeDir",
|
||||
"move",
|
||||
"copy",
|
||||
"delete",
|
||||
"open",
|
||||
"getDrive",
|
||||
"getFreeSpace",
|
||||
"find",
|
||||
"getDir",
|
||||
"getCapacity",
|
||||
"attributes",
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
|
||||
{
|
||||
switch( method )
|
||||
environment.addTrackingChange( TrackingField.FS_OPS );
|
||||
try
|
||||
{
|
||||
case 0:
|
||||
return fileSystem.list( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final String combine( String pathA, String pathB )
|
||||
{
|
||||
return fileSystem.combine( pathA, pathB );
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final String getName( String path )
|
||||
{
|
||||
return FileSystem.getName( path );
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final String getDir( String path )
|
||||
{
|
||||
return FileSystem.getDirectory( path );
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final long getSize( String path ) throws LuaException
|
||||
{
|
||||
try
|
||||
{
|
||||
return fileSystem.getSize( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final boolean exists( String path )
|
||||
{
|
||||
try
|
||||
{
|
||||
return fileSystem.exists( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final boolean isDir( String path )
|
||||
{
|
||||
try
|
||||
{
|
||||
return fileSystem.isDir( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final boolean isReadOnly( String path )
|
||||
{
|
||||
try
|
||||
{
|
||||
return fileSystem.isReadOnly( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final void makeDir( String path ) throws LuaException
|
||||
{
|
||||
try
|
||||
{
|
||||
environment.addTrackingChange( TrackingField.FS_OPS );
|
||||
fileSystem.makeDir( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final void move( String path, String dest ) throws LuaException
|
||||
{
|
||||
try
|
||||
{
|
||||
environment.addTrackingChange( TrackingField.FS_OPS );
|
||||
fileSystem.move( path, dest );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final void copy( String path, String dest ) throws LuaException
|
||||
{
|
||||
try
|
||||
{
|
||||
environment.addTrackingChange( TrackingField.FS_OPS );
|
||||
fileSystem.copy( path, dest );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final void delete( String path ) throws LuaException
|
||||
{
|
||||
try
|
||||
{
|
||||
environment.addTrackingChange( TrackingField.FS_OPS );
|
||||
fileSystem.delete( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final Object[] open( String path, String mode ) throws LuaException
|
||||
{
|
||||
environment.addTrackingChange( TrackingField.FS_OPS );
|
||||
try
|
||||
{
|
||||
switch( mode )
|
||||
{
|
||||
// list
|
||||
String path = getString( args, 0 );
|
||||
m_env.addTrackingChange( TrackingField.FS_OPS );
|
||||
try
|
||||
case "r":
|
||||
{
|
||||
return new Object[] { m_fileSystem.list( path ) };
|
||||
// Open the file for reading, then create a wrapper around the reader
|
||||
FileSystemWrapper<BufferedReader> reader = fileSystem.openForRead( path, EncodedReadableHandle::openUtf8 );
|
||||
return new Object[] { new EncodedReadableHandle( reader.get(), reader ) };
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
case "w":
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
// Open the file for writing, then create a wrapper around the writer
|
||||
FileSystemWrapper<BufferedWriter> writer = fileSystem.openForWrite( path, false, EncodedWritableHandle::openUtf8 );
|
||||
return new Object[] { new EncodedWritableHandle( writer.get(), writer ) };
|
||||
}
|
||||
case "a":
|
||||
{
|
||||
// Open the file for appending, then create a wrapper around the writer
|
||||
FileSystemWrapper<BufferedWriter> writer = fileSystem.openForWrite( path, true, EncodedWritableHandle::openUtf8 );
|
||||
return new Object[] { new EncodedWritableHandle( writer.get(), writer ) };
|
||||
}
|
||||
case "rb":
|
||||
{
|
||||
// Open the file for binary reading, then create a wrapper around the reader
|
||||
FileSystemWrapper<ReadableByteChannel> reader = fileSystem.openForRead( path, Function.identity() );
|
||||
return new Object[] { BinaryReadableHandle.of( reader.get(), reader ) };
|
||||
}
|
||||
case "wb":
|
||||
{
|
||||
// Open the file for binary writing, then create a wrapper around the writer
|
||||
FileSystemWrapper<WritableByteChannel> writer = fileSystem.openForWrite( path, false, Function.identity() );
|
||||
return new Object[] { BinaryWritableHandle.of( writer.get(), writer ) };
|
||||
}
|
||||
case "ab":
|
||||
{
|
||||
// Open the file for binary appending, then create a wrapper around the reader
|
||||
FileSystemWrapper<WritableByteChannel> writer = fileSystem.openForWrite( path, true, Function.identity() );
|
||||
return new Object[] { BinaryWritableHandle.of( writer.get(), writer ) };
|
||||
}
|
||||
default:
|
||||
throw new LuaException( "Unsupported mode" );
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
// combine
|
||||
String pathA = getString( args, 0 );
|
||||
String pathB = getString( args, 1 );
|
||||
return new Object[] { m_fileSystem.combine( pathA, pathB ) };
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
// getName
|
||||
String path = getString( args, 0 );
|
||||
return new Object[] { FileSystem.getName( path ) };
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
// getSize
|
||||
String path = getString( args, 0 );
|
||||
try
|
||||
{
|
||||
return new Object[] { m_fileSystem.getSize( path ) };
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
// exists
|
||||
String path = getString( args, 0 );
|
||||
try
|
||||
{
|
||||
return new Object[] { m_fileSystem.exists( path ) };
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
return new Object[] { false };
|
||||
}
|
||||
}
|
||||
case 5:
|
||||
{
|
||||
// isDir
|
||||
String path = getString( args, 0 );
|
||||
try
|
||||
{
|
||||
return new Object[] { m_fileSystem.isDir( path ) };
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
return new Object[] { false };
|
||||
}
|
||||
}
|
||||
case 6:
|
||||
{
|
||||
// isReadOnly
|
||||
String path = getString( args, 0 );
|
||||
try
|
||||
{
|
||||
return new Object[] { m_fileSystem.isReadOnly( path ) };
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
return new Object[] { false };
|
||||
}
|
||||
}
|
||||
case 7:
|
||||
{
|
||||
// makeDir
|
||||
String path = getString( args, 0 );
|
||||
try
|
||||
{
|
||||
m_env.addTrackingChange( TrackingField.FS_OPS );
|
||||
m_fileSystem.makeDir( path );
|
||||
return null;
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
case 8:
|
||||
{
|
||||
// move
|
||||
String path = getString( args, 0 );
|
||||
String dest = getString( args, 1 );
|
||||
try
|
||||
{
|
||||
m_env.addTrackingChange( TrackingField.FS_OPS );
|
||||
m_fileSystem.move( path, dest );
|
||||
return null;
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
case 9:
|
||||
{
|
||||
// copy
|
||||
String path = getString( args, 0 );
|
||||
String dest = getString( args, 1 );
|
||||
try
|
||||
{
|
||||
m_env.addTrackingChange( TrackingField.FS_OPS );
|
||||
m_fileSystem.copy( path, dest );
|
||||
return null;
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
case 10:
|
||||
{
|
||||
// delete
|
||||
String path = getString( args, 0 );
|
||||
try
|
||||
{
|
||||
m_env.addTrackingChange( TrackingField.FS_OPS );
|
||||
m_fileSystem.delete( path );
|
||||
return null;
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
case 11:
|
||||
{
|
||||
// open
|
||||
String path = getString( args, 0 );
|
||||
String mode = getString( args, 1 );
|
||||
m_env.addTrackingChange( TrackingField.FS_OPS );
|
||||
try
|
||||
{
|
||||
switch( mode )
|
||||
{
|
||||
case "r":
|
||||
{
|
||||
// Open the file for reading, then create a wrapper around the reader
|
||||
FileSystemWrapper<BufferedReader> reader = m_fileSystem.openForRead( path, EncodedReadableHandle::openUtf8 );
|
||||
return new Object[] { new EncodedReadableHandle( reader.get(), reader ) };
|
||||
}
|
||||
case "w":
|
||||
{
|
||||
// Open the file for writing, then create a wrapper around the writer
|
||||
FileSystemWrapper<BufferedWriter> writer = m_fileSystem.openForWrite( path, false, EncodedWritableHandle::openUtf8 );
|
||||
return new Object[] { new EncodedWritableHandle( writer.get(), writer ) };
|
||||
}
|
||||
case "a":
|
||||
{
|
||||
// Open the file for appending, then create a wrapper around the writer
|
||||
FileSystemWrapper<BufferedWriter> writer = m_fileSystem.openForWrite( path, true, EncodedWritableHandle::openUtf8 );
|
||||
return new Object[] { new EncodedWritableHandle( writer.get(), writer ) };
|
||||
}
|
||||
case "rb":
|
||||
{
|
||||
// Open the file for binary reading, then create a wrapper around the reader
|
||||
FileSystemWrapper<ReadableByteChannel> reader = m_fileSystem.openForRead( path, Function.identity() );
|
||||
return new Object[] { new BinaryReadableHandle( reader.get(), reader ) };
|
||||
}
|
||||
case "wb":
|
||||
{
|
||||
// Open the file for binary writing, then create a wrapper around the writer
|
||||
FileSystemWrapper<WritableByteChannel> writer = m_fileSystem.openForWrite( path, false, Function.identity() );
|
||||
return new Object[] { new BinaryWritableHandle( writer.get(), writer ) };
|
||||
}
|
||||
case "ab":
|
||||
{
|
||||
// Open the file for binary appending, then create a wrapper around the reader
|
||||
FileSystemWrapper<WritableByteChannel> writer = m_fileSystem.openForWrite( path, true, Function.identity() );
|
||||
return new Object[] { new BinaryWritableHandle( writer.get(), writer ) };
|
||||
}
|
||||
default:
|
||||
throw new LuaException( "Unsupported mode" );
|
||||
}
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
return new Object[] { null, e.getMessage() };
|
||||
}
|
||||
}
|
||||
case 12:
|
||||
{
|
||||
// getDrive
|
||||
String path = getString( args, 0 );
|
||||
try
|
||||
{
|
||||
if( !m_fileSystem.exists( path ) )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new Object[] { m_fileSystem.getMountLabel( path ) };
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
case 13:
|
||||
{
|
||||
// getFreeSpace
|
||||
String path = getString( args, 0 );
|
||||
try
|
||||
{
|
||||
long freeSpace = m_fileSystem.getFreeSpace( path );
|
||||
if( freeSpace >= 0 )
|
||||
{
|
||||
return new Object[] { freeSpace };
|
||||
}
|
||||
return new Object[] { "unlimited" };
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
case 14: // find
|
||||
{
|
||||
String path = getString( args, 0 );
|
||||
try
|
||||
{
|
||||
m_env.addTrackingChange( TrackingField.FS_OPS );
|
||||
return new Object[] { m_fileSystem.find( path ) };
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
case 15: // getDir
|
||||
{
|
||||
String path = getString( args, 0 );
|
||||
return new Object[] { FileSystem.getDirectory( path ) };
|
||||
}
|
||||
case 16: // getCapacity
|
||||
{
|
||||
String path = getString( args, 0 );
|
||||
try
|
||||
{
|
||||
OptionalLong capacity = m_fileSystem.getCapacity( path );
|
||||
return new Object[] { capacity.isPresent() ? capacity.getAsLong() : null };
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
case 17: // attributes
|
||||
{
|
||||
String path = getString( args, 0 );
|
||||
try
|
||||
{
|
||||
BasicFileAttributes attributes = m_fileSystem.getAttributes( path );
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put( "modification", getFileTime( attributes.lastModifiedTime() ) );
|
||||
result.put( "created", getFileTime( attributes.creationTime() ) );
|
||||
result.put( "size", attributes.isDirectory() ? 0 : attributes.size() );
|
||||
result.put( "isDir", attributes.isDirectory() );
|
||||
return new Object[] { result };
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
default:
|
||||
assert false;
|
||||
return null;
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
return new Object[] { null, e.getMessage() };
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final Object[] getDrive( String path ) throws LuaException
|
||||
{
|
||||
try
|
||||
{
|
||||
return fileSystem.exists( path ) ? new Object[] { fileSystem.getMountLabel( path ) } : null;
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final Object getFreeSpace( String path ) throws LuaException
|
||||
{
|
||||
try
|
||||
{
|
||||
long freeSpace = fileSystem.getFreeSpace( path );
|
||||
return freeSpace >= 0 ? freeSpace : "unlimited";
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final String[] find( String path ) throws LuaException
|
||||
{
|
||||
try
|
||||
{
|
||||
environment.addTrackingChange( TrackingField.FS_OPS );
|
||||
return fileSystem.find( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final Object getCapacity( String path ) throws LuaException
|
||||
{
|
||||
try
|
||||
{
|
||||
OptionalLong capacity = fileSystem.getCapacity( path );
|
||||
return capacity.isPresent() ? capacity.getAsLong() : null;
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final Map<String, Object> attributes( String path ) throws LuaException
|
||||
{
|
||||
try
|
||||
{
|
||||
BasicFileAttributes attributes = fileSystem.getAttributes( path );
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put( "modification", getFileTime( attributes.lastModifiedTime() ) );
|
||||
result.put( "created", getFileTime( attributes.creationTime() ) );
|
||||
result.put( "size", attributes.isDirectory() ? 0 : attributes.size() );
|
||||
result.put( "isDir", attributes.isDirectory() );
|
||||
return result;
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.IArguments;
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.core.apis.http.*;
|
||||
import dan200.computercraft.core.apis.http.request.HttpRequest;
|
||||
import dan200.computercraft.core.apis.http.websocket.Websocket;
|
||||
@@ -21,8 +22,8 @@ import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static dan200.computercraft.api.lua.ArgumentHelper.*;
|
||||
import static dan200.computercraft.core.apis.TableHelper.*;
|
||||
|
||||
public class HTTPAPI implements ILuaAPI
|
||||
@@ -68,135 +69,106 @@ public class HTTPAPI implements ILuaAPI
|
||||
Resource.cleanup();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
@LuaFunction
|
||||
public final Object[] request( IArguments args ) throws LuaException
|
||||
{
|
||||
return new String[] {
|
||||
"request",
|
||||
"checkURL",
|
||||
"websocket",
|
||||
};
|
||||
String address, postString, requestMethod;
|
||||
Map<?, ?> headerTable;
|
||||
boolean binary, redirect;
|
||||
|
||||
if( args.get( 0 ) instanceof Map )
|
||||
{
|
||||
Map<?, ?> options = args.getTable( 0 );
|
||||
address = getStringField( options, "url" );
|
||||
postString = optStringField( options, "body", null );
|
||||
headerTable = optTableField( options, "headers", Collections.emptyMap() );
|
||||
binary = optBooleanField( options, "binary", false );
|
||||
requestMethod = optStringField( options, "method", null );
|
||||
redirect = optBooleanField( options, "redirect", true );
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get URL and post information
|
||||
address = args.getString( 0 );
|
||||
postString = args.optString( 1, null );
|
||||
headerTable = args.optTable( 2, Collections.emptyMap() );
|
||||
binary = args.optBoolean( 3, false );
|
||||
requestMethod = null;
|
||||
redirect = true;
|
||||
}
|
||||
|
||||
HttpHeaders headers = getHeaders( headerTable );
|
||||
|
||||
HttpMethod httpMethod;
|
||||
if( requestMethod == null )
|
||||
{
|
||||
httpMethod = postString == null ? HttpMethod.GET : HttpMethod.POST;
|
||||
}
|
||||
else
|
||||
{
|
||||
httpMethod = HttpMethod.valueOf( requestMethod.toUpperCase( Locale.ROOT ) );
|
||||
if( httpMethod == null || requestMethod.equalsIgnoreCase( "CONNECT" ) )
|
||||
{
|
||||
throw new LuaException( "Unsupported HTTP method" );
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
URI uri = HttpRequest.checkUri( address );
|
||||
HttpRequest request = new HttpRequest( requests, m_apiEnvironment, address, postString, headers, binary, redirect );
|
||||
|
||||
// Make the request
|
||||
request.queue( r -> r.request( uri, httpMethod ) );
|
||||
|
||||
return new Object[] { true };
|
||||
}
|
||||
catch( HTTPRequestException e )
|
||||
{
|
||||
return new Object[] { false, e.getMessage() };
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings( "resource" )
|
||||
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
|
||||
@LuaFunction
|
||||
public final Object[] checkURL( String address )
|
||||
{
|
||||
switch( method )
|
||||
try
|
||||
{
|
||||
case 0: // request
|
||||
URI uri = HttpRequest.checkUri( address );
|
||||
new CheckUrl( checkUrls, m_apiEnvironment, address, uri ).queue( CheckUrl::run );
|
||||
|
||||
return new Object[] { true };
|
||||
}
|
||||
catch( HTTPRequestException e )
|
||||
{
|
||||
return new Object[] { false, e.getMessage() };
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final Object[] websocket( String address, Optional<Map<?, ?>> headerTbl ) throws LuaException
|
||||
{
|
||||
if( !ComputerCraft.httpWebsocketEnabled )
|
||||
{
|
||||
throw new LuaException( "Websocket connections are disabled" );
|
||||
}
|
||||
|
||||
HttpHeaders headers = getHeaders( headerTbl.orElse( Collections.emptyMap() ) );
|
||||
|
||||
try
|
||||
{
|
||||
URI uri = Websocket.checkUri( address );
|
||||
if( !new Websocket( websockets, m_apiEnvironment, uri, address, headers ).queue( Websocket::connect ) )
|
||||
{
|
||||
String address, postString, requestMethod;
|
||||
Map<?, ?> headerTable;
|
||||
boolean binary, redirect;
|
||||
|
||||
if( args.length >= 1 && args[0] instanceof Map )
|
||||
{
|
||||
Map<?, ?> options = (Map<?, ?>) args[0];
|
||||
address = getStringField( options, "url" );
|
||||
postString = optStringField( options, "body", null );
|
||||
headerTable = optTableField( options, "headers", Collections.emptyMap() );
|
||||
binary = optBooleanField( options, "binary", false );
|
||||
requestMethod = optStringField( options, "method", null );
|
||||
redirect = optBooleanField( options, "redirect", true );
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get URL and post information
|
||||
address = getString( args, 0 );
|
||||
postString = optString( args, 1, null );
|
||||
headerTable = optTable( args, 2, Collections.emptyMap() );
|
||||
binary = optBoolean( args, 3, false );
|
||||
requestMethod = null;
|
||||
redirect = true;
|
||||
}
|
||||
|
||||
HttpHeaders headers = getHeaders( headerTable );
|
||||
|
||||
|
||||
HttpMethod httpMethod;
|
||||
if( requestMethod == null )
|
||||
{
|
||||
httpMethod = postString == null ? HttpMethod.GET : HttpMethod.POST;
|
||||
}
|
||||
else
|
||||
{
|
||||
httpMethod = HttpMethod.valueOf( requestMethod.toUpperCase( Locale.ROOT ) );
|
||||
if( httpMethod == null || requestMethod.equalsIgnoreCase( "CONNECT" ) )
|
||||
{
|
||||
throw new LuaException( "Unsupported HTTP method" );
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
URI uri = HttpRequest.checkUri( address );
|
||||
HttpRequest request = new HttpRequest( requests, m_apiEnvironment, address, postString, headers, binary, redirect );
|
||||
|
||||
long requestBody = request.body().readableBytes() + HttpRequest.getHeaderSize( headers );
|
||||
if( ComputerCraft.httpMaxUpload != 0 && requestBody > ComputerCraft.httpMaxUpload )
|
||||
{
|
||||
throw new HTTPRequestException( "Request body is too large" );
|
||||
}
|
||||
|
||||
// Make the request
|
||||
request.queue( r -> r.request( uri, httpMethod ) );
|
||||
|
||||
return new Object[] { true };
|
||||
}
|
||||
catch( HTTPRequestException e )
|
||||
{
|
||||
return new Object[] { false, e.getMessage() };
|
||||
}
|
||||
throw new LuaException( "Too many websockets already open" );
|
||||
}
|
||||
case 1: // checkURL
|
||||
{
|
||||
String address = getString( args, 0 );
|
||||
|
||||
// Check URL
|
||||
try
|
||||
{
|
||||
URI uri = HttpRequest.checkUri( address );
|
||||
new CheckUrl( checkUrls, m_apiEnvironment, address, uri ).queue( CheckUrl::run );
|
||||
|
||||
return new Object[] { true };
|
||||
}
|
||||
catch( HTTPRequestException e )
|
||||
{
|
||||
return new Object[] { false, e.getMessage() };
|
||||
}
|
||||
}
|
||||
case 2: // websocket
|
||||
{
|
||||
String address = getString( args, 0 );
|
||||
Map<?, ?> headerTbl = optTable( args, 1, Collections.emptyMap() );
|
||||
|
||||
if( !ComputerCraft.httpWebsocketEnabled )
|
||||
{
|
||||
throw new LuaException( "Websocket connections are disabled" );
|
||||
}
|
||||
|
||||
HttpHeaders headers = getHeaders( headerTbl );
|
||||
|
||||
try
|
||||
{
|
||||
URI uri = Websocket.checkUri( address );
|
||||
if( !new Websocket( websockets, m_apiEnvironment, uri, address, headers ).queue( Websocket::connect ) )
|
||||
{
|
||||
throw new LuaException( "Too many websockets already open" );
|
||||
}
|
||||
|
||||
return new Object[] { true };
|
||||
}
|
||||
catch( HTTPRequestException e )
|
||||
{
|
||||
return new Object[] { false, e.getMessage() };
|
||||
}
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
return new Object[] { true };
|
||||
}
|
||||
catch( HTTPRequestException e )
|
||||
{
|
||||
return new Object[] { false, e.getMessage() };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ public interface IAPIEnvironment
|
||||
|
||||
void reboot();
|
||||
|
||||
void queueEvent( String event, Object[] args );
|
||||
void queueEvent( String event, Object... args );
|
||||
|
||||
void setOutput( ComputerSide side, int output );
|
||||
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
*/
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.IArguments;
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.shared.util.StringUtil;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
@@ -20,11 +21,11 @@ import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatterBuilder;
|
||||
import java.util.*;
|
||||
|
||||
import static dan200.computercraft.api.lua.ArgumentHelper.*;
|
||||
import static dan200.computercraft.api.lua.LuaValues.checkFinite;
|
||||
|
||||
public class OSAPI implements ILuaAPI
|
||||
{
|
||||
private IAPIEnvironment m_apiEnvironment;
|
||||
private final IAPIEnvironment apiEnvironment;
|
||||
|
||||
private final Int2ObjectMap<Alarm> m_alarms = new Int2ObjectOpenHashMap<>();
|
||||
private int m_clock;
|
||||
@@ -55,11 +56,9 @@ public class OSAPI implements ILuaAPI
|
||||
|
||||
public OSAPI( IAPIEnvironment environment )
|
||||
{
|
||||
m_apiEnvironment = environment;
|
||||
apiEnvironment = environment;
|
||||
}
|
||||
|
||||
// ILuaAPI implementation
|
||||
|
||||
@Override
|
||||
public String[] getNames()
|
||||
{
|
||||
@@ -69,8 +68,8 @@ public class OSAPI implements ILuaAPI
|
||||
@Override
|
||||
public void startup()
|
||||
{
|
||||
m_time = m_apiEnvironment.getComputerEnvironment().getTimeOfDay();
|
||||
m_day = m_apiEnvironment.getComputerEnvironment().getDay();
|
||||
m_time = apiEnvironment.getComputerEnvironment().getTimeOfDay();
|
||||
m_day = apiEnvironment.getComputerEnvironment().getDay();
|
||||
m_clock = 0;
|
||||
|
||||
synchronized( m_alarms )
|
||||
@@ -89,8 +88,8 @@ public class OSAPI implements ILuaAPI
|
||||
{
|
||||
double previousTime = m_time;
|
||||
int previousDay = m_day;
|
||||
double time = m_apiEnvironment.getComputerEnvironment().getTimeOfDay();
|
||||
int day = m_apiEnvironment.getComputerEnvironment().getDay();
|
||||
double time = apiEnvironment.getComputerEnvironment().getTimeOfDay();
|
||||
int day = apiEnvironment.getComputerEnvironment().getDay();
|
||||
|
||||
if( time > previousTime || day > previousDay )
|
||||
{
|
||||
@@ -103,7 +102,7 @@ public class OSAPI implements ILuaAPI
|
||||
double t = alarm.m_day * 24.0 + alarm.m_time;
|
||||
if( now >= t )
|
||||
{
|
||||
queueLuaEvent( "alarm", new Object[] { entry.getIntKey() } );
|
||||
apiEnvironment.queueEvent( "alarm", entry.getIntKey() );
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
@@ -123,31 +122,6 @@ public class OSAPI implements ILuaAPI
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
{
|
||||
return new String[] {
|
||||
"queueEvent",
|
||||
"startTimer",
|
||||
"setAlarm",
|
||||
"shutdown",
|
||||
"reboot",
|
||||
"computerID",
|
||||
"getComputerID",
|
||||
"setComputerLabel",
|
||||
"computerLabel",
|
||||
"getComputerLabel",
|
||||
"clock",
|
||||
"time",
|
||||
"day",
|
||||
"cancelTimer",
|
||||
"cancelAlarm",
|
||||
"epoch",
|
||||
"date",
|
||||
};
|
||||
}
|
||||
|
||||
private static float getTimeForCalendar( Calendar c )
|
||||
{
|
||||
float time = c.get( Calendar.HOUR_OF_DAY );
|
||||
@@ -174,214 +148,174 @@ public class OSAPI implements ILuaAPI
|
||||
return c.getTime().getTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
|
||||
@LuaFunction
|
||||
public final void queueEvent( String name, IArguments args )
|
||||
{
|
||||
switch( method )
|
||||
apiEnvironment.queueEvent( name, args.drop( 1 ).getAll() );
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final int startTimer( double timer ) throws LuaException
|
||||
{
|
||||
return apiEnvironment.startTimer( Math.round( checkFinite( 0, timer ) / 0.05 ) );
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final void cancelTimer( int token )
|
||||
{
|
||||
apiEnvironment.cancelTimer( token );
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final int setAlarm( double time ) throws LuaException
|
||||
{
|
||||
checkFinite( 0, time );
|
||||
if( time < 0.0 || time >= 24.0 ) throw new LuaException( "Number out of range" );
|
||||
synchronized( m_alarms )
|
||||
{
|
||||
case 0: // queueEvent
|
||||
queueLuaEvent( getString( args, 0 ), trimArray( args, 1 ) );
|
||||
return null;
|
||||
case 1:
|
||||
{
|
||||
// startTimer
|
||||
double timer = getFiniteDouble( args, 0 );
|
||||
int id = m_apiEnvironment.startTimer( Math.round( timer / 0.05 ) );
|
||||
return new Object[] { id };
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
// setAlarm
|
||||
double time = getFiniteDouble( args, 0 );
|
||||
if( time < 0.0 || time >= 24.0 )
|
||||
{
|
||||
throw new LuaException( "Number out of range" );
|
||||
}
|
||||
synchronized( m_alarms )
|
||||
{
|
||||
int day = time > m_time ? m_day : m_day + 1;
|
||||
m_alarms.put( m_nextAlarmToken, new Alarm( time, day ) );
|
||||
return new Object[] { m_nextAlarmToken++ };
|
||||
}
|
||||
}
|
||||
case 3: // shutdown
|
||||
m_apiEnvironment.shutdown();
|
||||
return null;
|
||||
case 4: // reboot
|
||||
m_apiEnvironment.reboot();
|
||||
return null;
|
||||
case 5:
|
||||
case 6: // computerID/getComputerID
|
||||
return new Object[] { getComputerID() };
|
||||
case 7:
|
||||
{
|
||||
// setComputerLabel
|
||||
String label = optString( args, 0, null );
|
||||
m_apiEnvironment.setLabel( StringUtil.normaliseLabel( label ) );
|
||||
return null;
|
||||
}
|
||||
case 8:
|
||||
case 9:
|
||||
{
|
||||
// computerLabel/getComputerLabel
|
||||
String label = m_apiEnvironment.getLabel();
|
||||
if( label != null )
|
||||
{
|
||||
return new Object[] { label };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case 10: // clock
|
||||
return new Object[] { m_clock * 0.05 };
|
||||
case 11:
|
||||
{
|
||||
// time
|
||||
Object value = args.length > 0 ? args[0] : null;
|
||||
if( value instanceof Map ) return new Object[] { LuaDateTime.fromTable( (Map<?, ?>) value ) };
|
||||
|
||||
String param = optString( args, 0, "ingame" );
|
||||
switch( param.toLowerCase( Locale.ROOT ) )
|
||||
{
|
||||
case "utc":
|
||||
{
|
||||
// Get Hour of day (UTC)
|
||||
Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
|
||||
return new Object[] { getTimeForCalendar( c ) };
|
||||
}
|
||||
case "local":
|
||||
{
|
||||
// Get Hour of day (local time)
|
||||
Calendar c = Calendar.getInstance();
|
||||
return new Object[] { getTimeForCalendar( c ) };
|
||||
}
|
||||
case "ingame":
|
||||
// Get ingame hour
|
||||
synchronized( m_alarms )
|
||||
{
|
||||
return new Object[] { m_time };
|
||||
}
|
||||
default:
|
||||
throw new LuaException( "Unsupported operation" );
|
||||
}
|
||||
}
|
||||
case 12:
|
||||
{
|
||||
// day
|
||||
String param = optString( args, 0, "ingame" );
|
||||
switch( param.toLowerCase( Locale.ROOT ) )
|
||||
{
|
||||
case "utc":
|
||||
{
|
||||
// Get numbers of days since 1970-01-01 (utc)
|
||||
Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
|
||||
return new Object[] { getDayForCalendar( c ) };
|
||||
}
|
||||
case "local":
|
||||
{
|
||||
// Get numbers of days since 1970-01-01 (local time)
|
||||
Calendar c = Calendar.getInstance();
|
||||
return new Object[] { getDayForCalendar( c ) };
|
||||
}
|
||||
case "ingame":
|
||||
// Get game day
|
||||
synchronized( m_alarms )
|
||||
{
|
||||
return new Object[] { m_day };
|
||||
}
|
||||
default:
|
||||
throw new LuaException( "Unsupported operation" );
|
||||
}
|
||||
}
|
||||
case 13:
|
||||
{
|
||||
// cancelTimer
|
||||
int token = getInt( args, 0 );
|
||||
m_apiEnvironment.cancelTimer( token );
|
||||
return null;
|
||||
}
|
||||
case 14:
|
||||
{
|
||||
// cancelAlarm
|
||||
int token = getInt( args, 0 );
|
||||
synchronized( m_alarms )
|
||||
{
|
||||
m_alarms.remove( token );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case 15: // epoch
|
||||
{
|
||||
String param = optString( args, 0, "ingame" );
|
||||
switch( param.toLowerCase( Locale.ROOT ) )
|
||||
{
|
||||
case "utc":
|
||||
{
|
||||
// Get utc epoch
|
||||
Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
|
||||
return new Object[] { getEpochForCalendar( c ) };
|
||||
}
|
||||
case "local":
|
||||
{
|
||||
// Get local epoch
|
||||
Calendar c = Calendar.getInstance();
|
||||
return new Object[] { getEpochForCalendar( c ) };
|
||||
}
|
||||
case "ingame":
|
||||
// Get in-game epoch
|
||||
synchronized( m_alarms )
|
||||
{
|
||||
return new Object[] { m_day * 86400000 + (int) (m_time * 3600000.0f) };
|
||||
}
|
||||
default:
|
||||
throw new LuaException( "Unsupported operation" );
|
||||
}
|
||||
}
|
||||
case 16: // date
|
||||
{
|
||||
String format = optString( args, 0, "%c" );
|
||||
long time = optLong( args, 1, Instant.now().getEpochSecond() );
|
||||
|
||||
Instant instant = Instant.ofEpochSecond( time );
|
||||
ZonedDateTime date;
|
||||
ZoneOffset offset;
|
||||
if( format.startsWith( "!" ) )
|
||||
{
|
||||
offset = ZoneOffset.UTC;
|
||||
date = ZonedDateTime.ofInstant( instant, offset );
|
||||
format = format.substring( 1 );
|
||||
}
|
||||
else
|
||||
{
|
||||
ZoneId id = ZoneId.systemDefault();
|
||||
offset = id.getRules().getOffset( instant );
|
||||
date = ZonedDateTime.ofInstant( instant, id );
|
||||
}
|
||||
|
||||
if( format.equals( "*t" ) ) return new Object[] { LuaDateTime.toTable( date, offset, instant ) };
|
||||
|
||||
DateTimeFormatterBuilder formatter = new DateTimeFormatterBuilder();
|
||||
LuaDateTime.format( formatter, format, offset );
|
||||
return new Object[] { formatter.toFormatter( Locale.ROOT ).format( date ) };
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
int day = time > m_time ? m_day : m_day + 1;
|
||||
m_alarms.put( m_nextAlarmToken, new Alarm( time, day ) );
|
||||
return m_nextAlarmToken++;
|
||||
}
|
||||
}
|
||||
|
||||
// Private methods
|
||||
|
||||
private void queueLuaEvent( String event, Object[] args )
|
||||
@LuaFunction
|
||||
public final void cancelAlarm( int token )
|
||||
{
|
||||
m_apiEnvironment.queueEvent( event, args );
|
||||
synchronized( m_alarms )
|
||||
{
|
||||
m_alarms.remove( token );
|
||||
}
|
||||
}
|
||||
|
||||
private Object[] trimArray( Object[] array, int skip )
|
||||
@LuaFunction( "shutdown" )
|
||||
public final void doShutdown()
|
||||
{
|
||||
return Arrays.copyOfRange( array, skip, array.length );
|
||||
apiEnvironment.shutdown();
|
||||
}
|
||||
|
||||
private int getComputerID()
|
||||
@LuaFunction( "reboot" )
|
||||
public final void doReboot()
|
||||
{
|
||||
return m_apiEnvironment.getComputerID();
|
||||
apiEnvironment.reboot();
|
||||
}
|
||||
|
||||
@LuaFunction( { "getComputerID", "computerID" } )
|
||||
public final int getComputerID()
|
||||
{
|
||||
return apiEnvironment.getComputerID();
|
||||
}
|
||||
|
||||
@LuaFunction( { "getComputerLabel", "computerLabel" } )
|
||||
public final Object[] getComputerLabel()
|
||||
{
|
||||
String label = apiEnvironment.getLabel();
|
||||
return label == null ? null : new Object[] { label };
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final void setComputerLabel( Optional<String> label )
|
||||
{
|
||||
apiEnvironment.setLabel( StringUtil.normaliseLabel( label.orElse( null ) ) );
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final double clock()
|
||||
{
|
||||
return m_clock * 0.05;
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final Object time( IArguments args ) throws LuaException
|
||||
{
|
||||
Object value = args.get( 0 );
|
||||
if( value instanceof Map ) return LuaDateTime.fromTable( (Map<?, ?>) value );
|
||||
|
||||
String param = args.optString( 0, "ingame" );
|
||||
switch( param.toLowerCase( Locale.ROOT ) )
|
||||
{
|
||||
case "utc": // Get Hour of day (UTC)
|
||||
return getTimeForCalendar( Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ) );
|
||||
case "local": // Get Hour of day (local time)
|
||||
return getTimeForCalendar( Calendar.getInstance() );
|
||||
case "ingame": // Get in-game hour
|
||||
return m_time;
|
||||
default:
|
||||
throw new LuaException( "Unsupported operation" );
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final int day( Optional<String> args ) throws LuaException
|
||||
{
|
||||
switch( args.orElse( "ingame" ).toLowerCase( Locale.ROOT ) )
|
||||
{
|
||||
case "utc": // Get numbers of days since 1970-01-01 (utc)
|
||||
return getDayForCalendar( Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ) );
|
||||
case "local": // Get numbers of days since 1970-01-01 (local time)
|
||||
return getDayForCalendar( Calendar.getInstance() );
|
||||
case "ingame":// Get game day
|
||||
return m_day;
|
||||
default:
|
||||
throw new LuaException( "Unsupported operation" );
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final long epoch( Optional<String> args ) throws LuaException
|
||||
{
|
||||
switch( args.orElse( "ingame" ).toLowerCase( Locale.ROOT ) )
|
||||
{
|
||||
case "utc":
|
||||
{
|
||||
// Get utc epoch
|
||||
Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
|
||||
return getEpochForCalendar( c );
|
||||
}
|
||||
case "local":
|
||||
{
|
||||
// Get local epoch
|
||||
Calendar c = Calendar.getInstance();
|
||||
return getEpochForCalendar( c );
|
||||
}
|
||||
case "ingame":
|
||||
// Get in-game epoch
|
||||
synchronized( m_alarms )
|
||||
{
|
||||
return m_day * 86400000 + (int) (m_time * 3600000.0f);
|
||||
}
|
||||
default:
|
||||
throw new LuaException( "Unsupported operation" );
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final Object date( Optional<String> formatA, Optional<Long> timeA ) throws LuaException
|
||||
{
|
||||
String format = formatA.orElse( "%c" );
|
||||
long time = timeA.orElseGet( () -> Instant.now().getEpochSecond() );
|
||||
|
||||
Instant instant = Instant.ofEpochSecond( time );
|
||||
ZonedDateTime date;
|
||||
ZoneOffset offset;
|
||||
if( format.startsWith( "!" ) )
|
||||
{
|
||||
offset = ZoneOffset.UTC;
|
||||
date = ZonedDateTime.ofInstant( instant, offset );
|
||||
format = format.substring( 1 );
|
||||
}
|
||||
else
|
||||
{
|
||||
ZoneId id = ZoneId.systemDefault();
|
||||
offset = id.getRules().getOffset( instant );
|
||||
date = ZonedDateTime.ofInstant( instant, id );
|
||||
}
|
||||
|
||||
if( format.equals( "*t" ) ) return LuaDateTime.toTable( date, offset, instant );
|
||||
|
||||
DateTimeFormatterBuilder formatter = new DateTimeFormatterBuilder();
|
||||
LuaDateTime.format( formatter, format, offset );
|
||||
return formatter.toFormatter( Locale.ROOT ).format( date );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,88 +7,74 @@ package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.filesystem.IMount;
|
||||
import dan200.computercraft.api.filesystem.IWritableMount;
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.*;
|
||||
import dan200.computercraft.api.peripheral.IDynamicPeripheral;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.peripheral.IWorkMonitor;
|
||||
import dan200.computercraft.api.peripheral.NotAttachedException;
|
||||
import dan200.computercraft.core.asm.LuaMethod;
|
||||
import dan200.computercraft.core.asm.NamedMethod;
|
||||
import dan200.computercraft.core.asm.PeripheralMethod;
|
||||
import dan200.computercraft.core.computer.ComputerSide;
|
||||
import dan200.computercraft.core.tracking.TrackingField;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static dan200.computercraft.api.lua.ArgumentHelper.getString;
|
||||
import java.util.*;
|
||||
|
||||
public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChangeListener
|
||||
{
|
||||
private class PeripheralWrapper extends ComputerAccess
|
||||
{
|
||||
private final String m_side;
|
||||
private final IPeripheral m_peripheral;
|
||||
private final String side;
|
||||
private final IPeripheral peripheral;
|
||||
|
||||
private String m_type;
|
||||
private String[] m_methods;
|
||||
private Map<String, Integer> m_methodMap;
|
||||
private boolean m_attached;
|
||||
private final String type;
|
||||
private final Map<String, PeripheralMethod> methodMap;
|
||||
private boolean attached;
|
||||
|
||||
PeripheralWrapper( IPeripheral peripheral, String side )
|
||||
{
|
||||
super( m_environment );
|
||||
m_side = side;
|
||||
m_peripheral = peripheral;
|
||||
m_attached = false;
|
||||
super( environment );
|
||||
this.side = side;
|
||||
this.peripheral = peripheral;
|
||||
attached = false;
|
||||
|
||||
m_type = peripheral.getType();
|
||||
m_methods = peripheral.getMethodNames();
|
||||
assert m_type != null;
|
||||
assert m_methods != null;
|
||||
type = Objects.requireNonNull( peripheral.getType(), "Peripheral type cannot be null" );
|
||||
|
||||
m_methodMap = new HashMap<>();
|
||||
for( int i = 0; i < m_methods.length; i++ )
|
||||
{
|
||||
if( m_methods[i] != null )
|
||||
{
|
||||
m_methodMap.put( m_methods[i], i );
|
||||
}
|
||||
}
|
||||
methodMap = PeripheralAPI.getMethods( peripheral );
|
||||
}
|
||||
|
||||
public IPeripheral getPeripheral()
|
||||
{
|
||||
return m_peripheral;
|
||||
return peripheral;
|
||||
}
|
||||
|
||||
public String getType()
|
||||
{
|
||||
return m_type;
|
||||
return type;
|
||||
}
|
||||
|
||||
public String[] getMethods()
|
||||
public Collection<String> getMethods()
|
||||
{
|
||||
return m_methods;
|
||||
return methodMap.keySet();
|
||||
}
|
||||
|
||||
public synchronized boolean isAttached()
|
||||
{
|
||||
return m_attached;
|
||||
return attached;
|
||||
}
|
||||
|
||||
public synchronized void attach()
|
||||
{
|
||||
m_attached = true;
|
||||
m_peripheral.attach( this );
|
||||
attached = true;
|
||||
peripheral.attach( this );
|
||||
}
|
||||
|
||||
public void detach()
|
||||
{
|
||||
// Call detach
|
||||
m_peripheral.detach( this );
|
||||
peripheral.detach( this );
|
||||
|
||||
synchronized( this )
|
||||
{
|
||||
@@ -96,63 +82,56 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
unmountAll();
|
||||
}
|
||||
|
||||
m_attached = false;
|
||||
attached = false;
|
||||
}
|
||||
|
||||
public Object[] call( ILuaContext context, String methodName, Object[] arguments ) throws LuaException, InterruptedException
|
||||
public MethodResult call( ILuaContext context, String methodName, IArguments arguments ) throws LuaException
|
||||
{
|
||||
int method = -1;
|
||||
PeripheralMethod method;
|
||||
synchronized( this )
|
||||
{
|
||||
if( m_methodMap.containsKey( methodName ) )
|
||||
{
|
||||
method = m_methodMap.get( methodName );
|
||||
}
|
||||
}
|
||||
if( method >= 0 )
|
||||
{
|
||||
m_environment.addTrackingChange( TrackingField.PERIPHERAL_OPS );
|
||||
return m_peripheral.callMethod( this, context, method, arguments );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new LuaException( "No such method " + methodName );
|
||||
method = methodMap.get( methodName );
|
||||
}
|
||||
|
||||
if( method == null ) throw new LuaException( "No such method " + methodName );
|
||||
|
||||
environment.addTrackingChange( TrackingField.PERIPHERAL_OPS );
|
||||
return method.apply( peripheral, context, this, arguments );
|
||||
}
|
||||
|
||||
// IComputerAccess implementation
|
||||
@Override
|
||||
public synchronized String mount( @Nonnull String desiredLoc, @Nonnull IMount mount, @Nonnull String driveName )
|
||||
{
|
||||
if( !m_attached ) throw new NotAttachedException();
|
||||
if( !attached ) throw new NotAttachedException();
|
||||
return super.mount( desiredLoc, mount, driveName );
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String mountWritable( @Nonnull String desiredLoc, @Nonnull IWritableMount mount, @Nonnull String driveName )
|
||||
{
|
||||
if( !m_attached ) throw new NotAttachedException();
|
||||
if( !attached ) throw new NotAttachedException();
|
||||
return super.mountWritable( desiredLoc, mount, driveName );
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void unmount( String location )
|
||||
{
|
||||
if( !m_attached ) throw new NotAttachedException();
|
||||
if( !attached ) throw new NotAttachedException();
|
||||
super.unmount( location );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getID()
|
||||
{
|
||||
if( !m_attached ) throw new NotAttachedException();
|
||||
if( !attached ) throw new NotAttachedException();
|
||||
return super.getID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queueEvent( @Nonnull final String event, final Object[] arguments )
|
||||
public void queueEvent( @Nonnull String event, Object... arguments )
|
||||
{
|
||||
if( !m_attached ) throw new NotAttachedException();
|
||||
if( !attached ) throw new NotAttachedException();
|
||||
super.queueEvent( event, arguments );
|
||||
}
|
||||
|
||||
@@ -160,18 +139,18 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
@Override
|
||||
public String getAttachmentName()
|
||||
{
|
||||
if( !m_attached ) throw new NotAttachedException();
|
||||
return m_side;
|
||||
if( !attached ) throw new NotAttachedException();
|
||||
return side;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Map<String, IPeripheral> getAvailablePeripherals()
|
||||
{
|
||||
if( !m_attached ) throw new NotAttachedException();
|
||||
if( !attached ) throw new NotAttachedException();
|
||||
|
||||
Map<String, IPeripheral> peripherals = new HashMap<>();
|
||||
for( PeripheralWrapper wrapper : m_peripherals )
|
||||
for( PeripheralWrapper wrapper : PeripheralAPI.this.peripherals )
|
||||
{
|
||||
if( wrapper != null && wrapper.isAttached() )
|
||||
{
|
||||
@@ -186,9 +165,9 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
@Override
|
||||
public IPeripheral getAvailablePeripheral( @Nonnull String name )
|
||||
{
|
||||
if( !m_attached ) throw new NotAttachedException();
|
||||
if( !attached ) throw new NotAttachedException();
|
||||
|
||||
for( PeripheralWrapper wrapper : m_peripherals )
|
||||
for( PeripheralWrapper wrapper : peripherals )
|
||||
{
|
||||
if( wrapper != null && wrapper.isAttached() && wrapper.getAttachmentName().equals( name ) )
|
||||
{
|
||||
@@ -202,27 +181,20 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
@Override
|
||||
public IWorkMonitor getMainThreadMonitor()
|
||||
{
|
||||
if( !m_attached ) throw new NotAttachedException();
|
||||
if( !attached ) throw new NotAttachedException();
|
||||
return super.getMainThreadMonitor();
|
||||
}
|
||||
}
|
||||
|
||||
private final IAPIEnvironment m_environment;
|
||||
private final PeripheralWrapper[] m_peripherals;
|
||||
private boolean m_running;
|
||||
private final IAPIEnvironment environment;
|
||||
private final PeripheralWrapper[] peripherals = new PeripheralWrapper[6];
|
||||
private boolean running;
|
||||
|
||||
public PeripheralAPI( IAPIEnvironment environment )
|
||||
{
|
||||
m_environment = environment;
|
||||
m_environment.setPeripheralChangeListener( this );
|
||||
|
||||
m_peripherals = new PeripheralWrapper[6];
|
||||
for( int i = 0; i < 6; i++ )
|
||||
{
|
||||
m_peripherals[i] = null;
|
||||
}
|
||||
|
||||
m_running = false;
|
||||
this.environment = environment;
|
||||
this.environment.setPeripheralChangeListener( this );
|
||||
running = false;
|
||||
}
|
||||
|
||||
// IPeripheralChangeListener
|
||||
@@ -230,37 +202,35 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
@Override
|
||||
public void onPeripheralChanged( ComputerSide side, IPeripheral newPeripheral )
|
||||
{
|
||||
synchronized( m_peripherals )
|
||||
synchronized( peripherals )
|
||||
{
|
||||
int index = side.ordinal();
|
||||
if( m_peripherals[index] != null )
|
||||
if( peripherals[index] != null )
|
||||
{
|
||||
// Queue a detachment
|
||||
final PeripheralWrapper wrapper = m_peripherals[index];
|
||||
final PeripheralWrapper wrapper = peripherals[index];
|
||||
if( wrapper.isAttached() ) wrapper.detach();
|
||||
|
||||
// Queue a detachment event
|
||||
m_environment.queueEvent( "peripheral_detach", new Object[] { side.getName() } );
|
||||
environment.queueEvent( "peripheral_detach", side.getName() );
|
||||
}
|
||||
|
||||
// Assign the new peripheral
|
||||
m_peripherals[index] = newPeripheral == null ? null
|
||||
peripherals[index] = newPeripheral == null ? null
|
||||
: new PeripheralWrapper( newPeripheral, side.getName() );
|
||||
|
||||
if( m_peripherals[index] != null )
|
||||
if( peripherals[index] != null )
|
||||
{
|
||||
// Queue an attachment
|
||||
final PeripheralWrapper wrapper = m_peripherals[index];
|
||||
if( m_running && !wrapper.isAttached() ) wrapper.attach();
|
||||
final PeripheralWrapper wrapper = peripherals[index];
|
||||
if( running && !wrapper.isAttached() ) wrapper.attach();
|
||||
|
||||
// Queue an attachment event
|
||||
m_environment.queueEvent( "peripheral", new Object[] { side.getName() } );
|
||||
environment.queueEvent( "peripheral", side.getName() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ILuaAPI implementation
|
||||
|
||||
@Override
|
||||
public String[] getNames()
|
||||
{
|
||||
@@ -270,12 +240,12 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
@Override
|
||||
public void startup()
|
||||
{
|
||||
synchronized( m_peripherals )
|
||||
synchronized( peripherals )
|
||||
{
|
||||
m_running = true;
|
||||
running = true;
|
||||
for( int i = 0; i < 6; i++ )
|
||||
{
|
||||
PeripheralWrapper wrapper = m_peripherals[i];
|
||||
PeripheralWrapper wrapper = peripherals[i];
|
||||
if( wrapper != null && !wrapper.isAttached() ) wrapper.attach();
|
||||
}
|
||||
}
|
||||
@@ -284,12 +254,12 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
@Override
|
||||
public void shutdown()
|
||||
{
|
||||
synchronized( m_peripherals )
|
||||
synchronized( peripherals )
|
||||
{
|
||||
m_running = false;
|
||||
running = false;
|
||||
for( int i = 0; i < 6; i++ )
|
||||
{
|
||||
PeripheralWrapper wrapper = m_peripherals[i];
|
||||
PeripheralWrapper wrapper = peripherals[i];
|
||||
if( wrapper != null && wrapper.isAttached() )
|
||||
{
|
||||
wrapper.detach();
|
||||
@@ -298,98 +268,95 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
@LuaFunction
|
||||
public final boolean isPresent( String sideName )
|
||||
{
|
||||
return new String[] {
|
||||
"isPresent",
|
||||
"getType",
|
||||
"getMethods",
|
||||
"call",
|
||||
};
|
||||
ComputerSide side = ComputerSide.valueOfInsensitive( sideName );
|
||||
if( side != null )
|
||||
{
|
||||
synchronized( peripherals )
|
||||
{
|
||||
PeripheralWrapper p = peripherals[side.ordinal()];
|
||||
if( p != null ) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException, InterruptedException
|
||||
@LuaFunction
|
||||
public final Object[] getType( String sideName )
|
||||
{
|
||||
switch( method )
|
||||
ComputerSide side = ComputerSide.valueOfInsensitive( sideName );
|
||||
if( side == null ) return null;
|
||||
|
||||
synchronized( peripherals )
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
// isPresent
|
||||
boolean present = false;
|
||||
ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
|
||||
if( side != null )
|
||||
{
|
||||
synchronized( m_peripherals )
|
||||
{
|
||||
PeripheralWrapper p = m_peripherals[side.ordinal()];
|
||||
if( p != null ) present = true;
|
||||
}
|
||||
}
|
||||
return new Object[] { present };
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
// getType
|
||||
ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
|
||||
if( side != null )
|
||||
{
|
||||
synchronized( m_peripherals )
|
||||
{
|
||||
PeripheralWrapper p = m_peripherals[side.ordinal()];
|
||||
if( p != null ) return new Object[] { p.getType() };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
// getMethods
|
||||
ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
|
||||
if( side != null )
|
||||
{
|
||||
synchronized( m_peripherals )
|
||||
{
|
||||
PeripheralWrapper p = m_peripherals[side.ordinal()];
|
||||
if( p != null ) return new Object[] { p.getMethods() };
|
||||
}
|
||||
}
|
||||
PeripheralWrapper p = peripherals[side.ordinal()];
|
||||
if( p != null ) return new Object[] { p.getType() };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
// call
|
||||
ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
|
||||
String methodName = getString( args, 1 );
|
||||
Object[] methodArgs = Arrays.copyOfRange( args, 2, args.length );
|
||||
@LuaFunction
|
||||
public final Object[] getMethods( String sideName )
|
||||
{
|
||||
ComputerSide side = ComputerSide.valueOfInsensitive( sideName );
|
||||
if( side == null ) return null;
|
||||
|
||||
if( side == null ) throw new LuaException( "No peripheral attached" );
|
||||
synchronized( peripherals )
|
||||
{
|
||||
PeripheralWrapper p = peripherals[side.ordinal()];
|
||||
if( p != null ) return new Object[] { p.getMethods() };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
PeripheralWrapper p;
|
||||
synchronized( m_peripherals )
|
||||
{
|
||||
p = m_peripherals[side.ordinal()];
|
||||
}
|
||||
if( p == null ) throw new LuaException( "No peripheral attached" );
|
||||
@LuaFunction
|
||||
public final MethodResult call( ILuaContext context, IArguments args ) throws LuaException
|
||||
{
|
||||
ComputerSide side = ComputerSide.valueOfInsensitive( args.getString( 0 ) );
|
||||
String methodName = args.getString( 1 );
|
||||
IArguments methodArgs = args.drop( 2 );
|
||||
|
||||
try
|
||||
{
|
||||
return p.call( context, methodName, methodArgs );
|
||||
}
|
||||
catch( LuaException e )
|
||||
{
|
||||
// We increase the error level by one in order to shift the error level to where peripheral.call was
|
||||
// invoked. It would be possible to do it in Lua code, but would add significantly more overhead.
|
||||
if( e.getLevel() > 0 ) throw new FastLuaException( e.getMessage(), e.getLevel() + 1 );
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
if( side == null ) throw new LuaException( "No peripheral attached" );
|
||||
|
||||
PeripheralWrapper p;
|
||||
synchronized( peripherals )
|
||||
{
|
||||
p = peripherals[side.ordinal()];
|
||||
}
|
||||
if( p == null ) throw new LuaException( "No peripheral attached" );
|
||||
|
||||
try
|
||||
{
|
||||
return p.call( context, methodName, methodArgs ).adjustError( 1 );
|
||||
}
|
||||
catch( LuaException e )
|
||||
{
|
||||
// We increase the error level by one in order to shift the error level to where peripheral.call was
|
||||
// invoked. It would be possible to do it in Lua code, but would add significantly more overhead.
|
||||
if( e.getLevel() > 0 ) throw new FastLuaException( e.getMessage(), e.getLevel() + 1 );
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, PeripheralMethod> getMethods( IPeripheral peripheral )
|
||||
{
|
||||
String[] dynamicMethods = peripheral instanceof IDynamicPeripheral
|
||||
? Objects.requireNonNull( ((IDynamicPeripheral) peripheral).getMethodNames(), "Peripheral methods cannot be null" )
|
||||
: LuaMethod.EMPTY_METHODS;
|
||||
|
||||
List<NamedMethod<PeripheralMethod>> methods = PeripheralMethod.GENERATOR.getMethods( peripheral.getClass() );
|
||||
|
||||
Map<String, PeripheralMethod> methodMap = new HashMap<>( methods.size() + dynamicMethods.length );
|
||||
for( int i = 0; i < dynamicMethods.length; i++ )
|
||||
{
|
||||
methodMap.put( dynamicMethods[i], PeripheralMethod.DYNAMIC.get( i ) );
|
||||
}
|
||||
for( NamedMethod<PeripheralMethod> method : methods )
|
||||
{
|
||||
methodMap.put( method.getName(), method.getMethod() );
|
||||
}
|
||||
return methodMap;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,21 +6,17 @@
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.core.computer.ComputerSide;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import static dan200.computercraft.api.lua.ArgumentHelper.*;
|
||||
|
||||
public class RedstoneAPI implements ILuaAPI
|
||||
{
|
||||
private IAPIEnvironment m_environment;
|
||||
private final IAPIEnvironment environment;
|
||||
|
||||
public RedstoneAPI( IAPIEnvironment environment )
|
||||
{
|
||||
m_environment = environment;
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -29,95 +25,71 @@ public class RedstoneAPI implements ILuaAPI
|
||||
return new String[] { "rs", "redstone" };
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
@LuaFunction
|
||||
public final String[] getSides()
|
||||
{
|
||||
return new String[] {
|
||||
"getSides",
|
||||
"setOutput",
|
||||
"getOutput",
|
||||
"getInput",
|
||||
"setBundledOutput",
|
||||
"getBundledOutput",
|
||||
"getBundledInput",
|
||||
"testBundledInput",
|
||||
"setAnalogOutput",
|
||||
"setAnalogueOutput",
|
||||
"getAnalogOutput",
|
||||
"getAnalogueOutput",
|
||||
"getAnalogInput",
|
||||
"getAnalogueInput",
|
||||
};
|
||||
return ComputerSide.NAMES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
|
||||
@LuaFunction
|
||||
public final void setOutput( ComputerSide side, boolean output )
|
||||
{
|
||||
switch( method )
|
||||
{
|
||||
case 0: // getSides
|
||||
return new Object[] { ComputerSide.NAMES };
|
||||
case 1:
|
||||
{
|
||||
// setOutput
|
||||
ComputerSide side = parseSide( args );
|
||||
boolean output = getBoolean( args, 1 );
|
||||
m_environment.setOutput( side, output ? 15 : 0 );
|
||||
return null;
|
||||
}
|
||||
case 2: // getOutput
|
||||
return new Object[] { m_environment.getOutput( parseSide( args ) ) > 0 };
|
||||
case 3: // getInput
|
||||
return new Object[] { m_environment.getInput( parseSide( args ) ) > 0 };
|
||||
case 4:
|
||||
{
|
||||
// setBundledOutput
|
||||
ComputerSide side = parseSide( args );
|
||||
int output = getInt( args, 1 );
|
||||
m_environment.setBundledOutput( side, output );
|
||||
return null;
|
||||
}
|
||||
case 5: // getBundledOutput
|
||||
return new Object[] { m_environment.getBundledOutput( parseSide( args ) ) };
|
||||
case 6: // getBundledInput
|
||||
return new Object[] { m_environment.getBundledInput( parseSide( args ) ) };
|
||||
case 7:
|
||||
{
|
||||
// testBundledInput
|
||||
ComputerSide side = parseSide( args );
|
||||
int mask = getInt( args, 1 );
|
||||
int input = m_environment.getBundledInput( side );
|
||||
return new Object[] { (input & mask) == mask };
|
||||
}
|
||||
case 8:
|
||||
case 9:
|
||||
{
|
||||
// setAnalogOutput/setAnalogueOutput
|
||||
ComputerSide side = parseSide( args );
|
||||
int output = getInt( args, 1 );
|
||||
if( output < 0 || output > 15 )
|
||||
{
|
||||
throw new LuaException( "Expected number in range 0-15" );
|
||||
}
|
||||
m_environment.setOutput( side, output );
|
||||
return null;
|
||||
}
|
||||
case 10:
|
||||
case 11: // getAnalogOutput/getAnalogueOutput
|
||||
return new Object[] { m_environment.getOutput( parseSide( args ) ) };
|
||||
case 12:
|
||||
case 13: // getAnalogInput/getAnalogueInput
|
||||
return new Object[] { m_environment.getInput( parseSide( args ) ) };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
environment.setOutput( side, output ? 15 : 0 );
|
||||
}
|
||||
|
||||
private static ComputerSide parseSide( Object[] args ) throws LuaException
|
||||
@LuaFunction
|
||||
public final boolean getOutput( ComputerSide side )
|
||||
{
|
||||
ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
|
||||
if( side == null ) throw new LuaException( "Invalid side." );
|
||||
return side;
|
||||
return environment.getOutput( side ) > 0;
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final boolean getInput( ComputerSide side )
|
||||
{
|
||||
return environment.getInput( side ) > 0;
|
||||
}
|
||||
|
||||
@LuaFunction( { "setAnalogOutput", "setAnalogueOutput" } )
|
||||
public final void setAnalogOutput( ComputerSide side, int output ) throws LuaException
|
||||
{
|
||||
if( output < 0 || output > 15 ) throw new LuaException( "Expected number in range 0-15" );
|
||||
environment.setOutput( side, output );
|
||||
}
|
||||
|
||||
@LuaFunction( { "getAnalogOutput", "getAnalogueOutput" } )
|
||||
public final int getAnalogOutput( ComputerSide side )
|
||||
{
|
||||
return environment.getOutput( side );
|
||||
}
|
||||
|
||||
@LuaFunction( { "getAnalogInput", "getAnalogueInput" } )
|
||||
public final int getAnalogInput( ComputerSide side )
|
||||
{
|
||||
return environment.getInput( side );
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final void setBundledOutput( ComputerSide side, int output )
|
||||
{
|
||||
environment.setBundledOutput( side, output );
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final int getBundledOutput( ComputerSide side )
|
||||
{
|
||||
return environment.getBundledOutput( side );
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final int getBundledInput( ComputerSide side )
|
||||
{
|
||||
return environment.getBundledOutput( side );
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final boolean testBundledInput( ComputerSide side, int mask )
|
||||
{
|
||||
int input = environment.getBundledInput( side );
|
||||
return (input & mask) == mask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
*/
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.ArgumentHelper;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaValues;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Map;
|
||||
|
||||
import static dan200.computercraft.api.lua.ArgumentHelper.getNumericType;
|
||||
import static dan200.computercraft.api.lua.LuaValues.getNumericType;
|
||||
|
||||
/**
|
||||
* Various helpers for tables.
|
||||
@@ -27,7 +27,7 @@ public final class TableHelper
|
||||
@Nonnull
|
||||
public static LuaException badKey( @Nonnull String key, @Nonnull String expected, @Nullable Object actual )
|
||||
{
|
||||
return badKey( key, expected, ArgumentHelper.getType( actual ) );
|
||||
return badKey( key, expected, LuaValues.getType( actual ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
||||
@@ -6,27 +6,23 @@
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.core.computer.IComputerEnvironment;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.shared.util.Colour;
|
||||
import dan200.computercraft.shared.util.Palette;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import static dan200.computercraft.api.lua.ArgumentHelper.*;
|
||||
|
||||
public class TermAPI implements ILuaAPI
|
||||
public class TermAPI extends TermMethods implements ILuaAPI
|
||||
{
|
||||
private final Terminal m_terminal;
|
||||
private final IComputerEnvironment m_environment;
|
||||
private final Terminal terminal;
|
||||
private final IComputerEnvironment environment;
|
||||
|
||||
public TermAPI( IAPIEnvironment environment )
|
||||
{
|
||||
m_terminal = environment.getTerminal();
|
||||
m_environment = environment.getComputerEnvironment();
|
||||
terminal = environment.getTerminal();
|
||||
this.environment = environment.getComputerEnvironment();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -35,262 +31,29 @@ public class TermAPI implements ILuaAPI
|
||||
return new String[] { "term" };
|
||||
}
|
||||
|
||||
@LuaFunction( { "nativePaletteColour", "nativePaletteColor" } )
|
||||
public final Object[] nativePaletteColour( int colourArg ) throws LuaException
|
||||
{
|
||||
int colour = 15 - parseColour( colourArg );
|
||||
Colour c = Colour.fromInt( colour );
|
||||
|
||||
float[] rgb = c.getRGB();
|
||||
|
||||
Object[] rgbObj = new Object[rgb.length];
|
||||
for( int i = 0; i < rgbObj.length; ++i ) rgbObj[i] = rgb[i];
|
||||
return rgbObj;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
public Terminal getTerminal()
|
||||
{
|
||||
return new String[] {
|
||||
"write",
|
||||
"scroll",
|
||||
"setCursorPos",
|
||||
"setCursorBlink",
|
||||
"getCursorPos",
|
||||
"getSize",
|
||||
"clear",
|
||||
"clearLine",
|
||||
"setTextColour",
|
||||
"setTextColor",
|
||||
"setBackgroundColour",
|
||||
"setBackgroundColor",
|
||||
"isColour",
|
||||
"isColor",
|
||||
"getTextColour",
|
||||
"getTextColor",
|
||||
"getBackgroundColour",
|
||||
"getBackgroundColor",
|
||||
"blit",
|
||||
"setPaletteColour",
|
||||
"setPaletteColor",
|
||||
"getPaletteColour",
|
||||
"getPaletteColor",
|
||||
"nativePaletteColour",
|
||||
"nativePaletteColor",
|
||||
"getCursorBlink",
|
||||
};
|
||||
}
|
||||
|
||||
public static int parseColour( Object[] args ) throws LuaException
|
||||
{
|
||||
int colour = getInt( args, 0 );
|
||||
if( colour <= 0 )
|
||||
{
|
||||
throw new LuaException( "Colour out of range" );
|
||||
}
|
||||
colour = getHighestBit( colour ) - 1;
|
||||
if( colour < 0 || colour > 15 )
|
||||
{
|
||||
throw new LuaException( "Colour out of range" );
|
||||
}
|
||||
return colour;
|
||||
}
|
||||
|
||||
public static Object[] encodeColour( int colour ) throws LuaException
|
||||
{
|
||||
return new Object[] { 1 << colour };
|
||||
}
|
||||
|
||||
public static void setColour( Terminal terminal, int colour, double r, double g, double b )
|
||||
{
|
||||
if( terminal.getPalette() != null )
|
||||
{
|
||||
terminal.getPalette().setColour( colour, r, g, b );
|
||||
terminal.setChanged();
|
||||
}
|
||||
return terminal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
|
||||
public boolean isColour()
|
||||
{
|
||||
switch( method )
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
// write
|
||||
String text = args.length > 0 && args[0] != null ? args[0].toString() : "";
|
||||
synchronized( m_terminal )
|
||||
{
|
||||
m_terminal.write( text );
|
||||
m_terminal.setCursorPos( m_terminal.getCursorX() + text.length(), m_terminal.getCursorY() );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
// scroll
|
||||
int y = getInt( args, 0 );
|
||||
synchronized( m_terminal )
|
||||
{
|
||||
m_terminal.scroll( y );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
// setCursorPos
|
||||
int x = getInt( args, 0 ) - 1;
|
||||
int y = getInt( args, 1 ) - 1;
|
||||
synchronized( m_terminal )
|
||||
{
|
||||
m_terminal.setCursorPos( x, y );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
// setCursorBlink
|
||||
boolean b = getBoolean( args, 0 );
|
||||
synchronized( m_terminal )
|
||||
{
|
||||
m_terminal.setCursorBlink( b );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
// getCursorPos
|
||||
int x, y;
|
||||
synchronized( m_terminal )
|
||||
{
|
||||
x = m_terminal.getCursorX();
|
||||
y = m_terminal.getCursorY();
|
||||
}
|
||||
return new Object[] { x + 1, y + 1 };
|
||||
}
|
||||
case 5:
|
||||
{
|
||||
// getSize
|
||||
int width, height;
|
||||
synchronized( m_terminal )
|
||||
{
|
||||
width = m_terminal.getWidth();
|
||||
height = m_terminal.getHeight();
|
||||
}
|
||||
return new Object[] { width, height };
|
||||
}
|
||||
case 6: // clear
|
||||
synchronized( m_terminal )
|
||||
{
|
||||
m_terminal.clear();
|
||||
}
|
||||
return null;
|
||||
case 7: // clearLine
|
||||
synchronized( m_terminal )
|
||||
{
|
||||
m_terminal.clearLine();
|
||||
}
|
||||
return null;
|
||||
case 8:
|
||||
case 9:
|
||||
{
|
||||
// setTextColour/setTextColor
|
||||
int colour = parseColour( args );
|
||||
synchronized( m_terminal )
|
||||
{
|
||||
m_terminal.setTextColour( colour );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case 10:
|
||||
case 11:
|
||||
{
|
||||
// setBackgroundColour/setBackgroundColor
|
||||
int colour = parseColour( args );
|
||||
synchronized( m_terminal )
|
||||
{
|
||||
m_terminal.setBackgroundColour( colour );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case 12:
|
||||
case 13: // isColour/isColor
|
||||
return new Object[] { m_environment.isColour() };
|
||||
case 14:
|
||||
case 15: // getTextColour/getTextColor
|
||||
return encodeColour( m_terminal.getTextColour() );
|
||||
case 16:
|
||||
case 17: // getBackgroundColour/getBackgroundColor
|
||||
return encodeColour( m_terminal.getBackgroundColour() );
|
||||
case 18:
|
||||
{
|
||||
// blit
|
||||
String text = getString( args, 0 );
|
||||
String textColour = getString( args, 1 );
|
||||
String backgroundColour = getString( args, 2 );
|
||||
if( textColour.length() != text.length() || backgroundColour.length() != text.length() )
|
||||
{
|
||||
throw new LuaException( "Arguments must be the same length" );
|
||||
}
|
||||
|
||||
synchronized( m_terminal )
|
||||
{
|
||||
m_terminal.blit( text, textColour, backgroundColour );
|
||||
m_terminal.setCursorPos( m_terminal.getCursorX() + text.length(), m_terminal.getCursorY() );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case 19:
|
||||
case 20:
|
||||
{
|
||||
// setPaletteColour/setPaletteColor
|
||||
int colour = 15 - parseColour( args );
|
||||
if( args.length == 2 )
|
||||
{
|
||||
int hex = getInt( args, 1 );
|
||||
double[] rgb = Palette.decodeRGB8( hex );
|
||||
setColour( m_terminal, colour, rgb[0], rgb[1], rgb[2] );
|
||||
}
|
||||
else
|
||||
{
|
||||
double r = getFiniteDouble( args, 1 );
|
||||
double g = getFiniteDouble( args, 2 );
|
||||
double b = getFiniteDouble( args, 3 );
|
||||
setColour( m_terminal, colour, r, g, b );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case 21:
|
||||
case 22:
|
||||
{
|
||||
// getPaletteColour/getPaletteColor
|
||||
int colour = 15 - parseColour( args );
|
||||
synchronized( m_terminal )
|
||||
{
|
||||
if( m_terminal.getPalette() != null )
|
||||
{
|
||||
return ArrayUtils.toObject( m_terminal.getPalette().getColour( colour ) );
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case 23:
|
||||
case 24:
|
||||
{
|
||||
// nativePaletteColour/nativePaletteColor
|
||||
int colour = 15 - parseColour( args );
|
||||
Colour c = Colour.fromInt( colour );
|
||||
|
||||
float[] rgb = c.getRGB();
|
||||
|
||||
Object[] rgbObj = new Object[rgb.length];
|
||||
for( int i = 0; i < rgbObj.length; ++i ) rgbObj[i] = rgb[i];
|
||||
return rgbObj;
|
||||
}
|
||||
case 25:
|
||||
// getCursorBlink
|
||||
return new Object[] { m_terminal.getCursorBlink() };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static int getHighestBit( int group )
|
||||
{
|
||||
int bit = 0;
|
||||
while( group > 0 )
|
||||
{
|
||||
group >>= 1;
|
||||
bit++;
|
||||
}
|
||||
return bit;
|
||||
return environment.isColour();
|
||||
}
|
||||
}
|
||||
|
||||
222
src/main/java/dan200/computercraft/core/apis/TermMethods.java
Normal file
222
src/main/java/dan200/computercraft/core/apis/TermMethods.java
Normal file
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.IArguments;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.shared.util.Palette;
|
||||
import dan200.computercraft.shared.util.StringUtil;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* A base class for all objects which interact with a terminal. Namely the {@link TermAPI} and monitors.
|
||||
*/
|
||||
public abstract class TermMethods
|
||||
{
|
||||
private static int getHighestBit( int group )
|
||||
{
|
||||
int bit = 0;
|
||||
while( group > 0 )
|
||||
{
|
||||
group >>= 1;
|
||||
bit++;
|
||||
}
|
||||
return bit;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public abstract Terminal getTerminal() throws LuaException;
|
||||
|
||||
public abstract boolean isColour() throws LuaException;
|
||||
|
||||
@LuaFunction
|
||||
public final void write( IArguments arguments ) throws LuaException
|
||||
{
|
||||
String text = StringUtil.toString( arguments.get( 0 ) );
|
||||
Terminal terminal = getTerminal();
|
||||
synchronized( terminal )
|
||||
{
|
||||
terminal.write( text );
|
||||
terminal.setCursorPos( terminal.getCursorX() + text.length(), terminal.getCursorY() );
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final void scroll( int y ) throws LuaException
|
||||
{
|
||||
getTerminal().scroll( y );
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final Object[] getCursorPos() throws LuaException
|
||||
{
|
||||
Terminal terminal = getTerminal();
|
||||
return new Object[] { terminal.getCursorX() + 1, terminal.getCursorY() + 1 };
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final void setCursorPos( int x, int y ) throws LuaException
|
||||
{
|
||||
Terminal terminal = getTerminal();
|
||||
synchronized( terminal )
|
||||
{
|
||||
terminal.setCursorPos( x - 1, y - 1 );
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final boolean getCursorBlink() throws LuaException
|
||||
{
|
||||
return getTerminal().getCursorBlink();
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final void setCursorBlink( boolean blink ) throws LuaException
|
||||
{
|
||||
Terminal terminal = getTerminal();
|
||||
synchronized( terminal )
|
||||
{
|
||||
terminal.setCursorBlink( blink );
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final Object[] getSize() throws LuaException
|
||||
{
|
||||
Terminal terminal = getTerminal();
|
||||
return new Object[] { terminal.getWidth(), terminal.getHeight() };
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final void clear() throws LuaException
|
||||
{
|
||||
getTerminal().clear();
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final void clearLine() throws LuaException
|
||||
{
|
||||
getTerminal().clearLine();
|
||||
}
|
||||
|
||||
@LuaFunction( { "getTextColour", "getTextColor" } )
|
||||
public final int getTextColour() throws LuaException
|
||||
{
|
||||
return encodeColour( getTerminal().getTextColour() );
|
||||
}
|
||||
|
||||
@LuaFunction( { "setTextColour", "setTextColor" } )
|
||||
public final void setTextColour( int colourArg ) throws LuaException
|
||||
{
|
||||
int colour = parseColour( colourArg );
|
||||
Terminal terminal = getTerminal();
|
||||
synchronized( terminal )
|
||||
{
|
||||
terminal.setTextColour( colour );
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction( { "getBackgroundColour", "getBackgroundColor" } )
|
||||
public final int getBackgroundColour() throws LuaException
|
||||
{
|
||||
return encodeColour( getTerminal().getBackgroundColour() );
|
||||
}
|
||||
|
||||
@LuaFunction( { "setBackgroundColour", "setBackgroundColor" } )
|
||||
public final void setBackgroundColour( int colourArg ) throws LuaException
|
||||
{
|
||||
int colour = parseColour( colourArg );
|
||||
Terminal terminal = getTerminal();
|
||||
synchronized( terminal )
|
||||
{
|
||||
terminal.setBackgroundColour( colour );
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction( { "isColour", "isColor" } )
|
||||
public final boolean getIsColour() throws LuaException
|
||||
{
|
||||
return isColour();
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final void blit( String text, String textColour, String backgroundColour ) throws LuaException
|
||||
{
|
||||
if( textColour.length() != text.length() || backgroundColour.length() != text.length() )
|
||||
{
|
||||
throw new LuaException( "Arguments must be the same length" );
|
||||
}
|
||||
|
||||
Terminal terminal = getTerminal();
|
||||
synchronized( terminal )
|
||||
{
|
||||
terminal.blit( text, textColour, backgroundColour );
|
||||
terminal.setCursorPos( terminal.getCursorX() + text.length(), terminal.getCursorY() );
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction( { "setPaletteColour", "setPaletteColor" } )
|
||||
public final void setPaletteColour( IArguments args ) throws LuaException
|
||||
{
|
||||
int colour = 15 - parseColour( args.getInt( 0 ) );
|
||||
if( args.count() == 2 )
|
||||
{
|
||||
int hex = args.getInt( 1 );
|
||||
double[] rgb = Palette.decodeRGB8( hex );
|
||||
setColour( getTerminal(), colour, rgb[0], rgb[1], rgb[2] );
|
||||
}
|
||||
else
|
||||
{
|
||||
double r = args.getFiniteDouble( 1 );
|
||||
double g = args.getFiniteDouble( 2 );
|
||||
double b = args.getFiniteDouble( 3 );
|
||||
setColour( getTerminal(), colour, r, g, b );
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction( { "getPaletteColour", "getPaletteColor" } )
|
||||
public final Object[] getPaletteColour( int colourArg ) throws LuaException
|
||||
{
|
||||
int colour = 15 - parseColour( colourArg );
|
||||
Terminal terminal = getTerminal();
|
||||
synchronized( terminal )
|
||||
{
|
||||
if( terminal.getPalette() != null )
|
||||
{
|
||||
return ArrayUtils.toObject( terminal.getPalette().getColour( colour ) );
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static int parseColour( int colour ) throws LuaException
|
||||
{
|
||||
if( colour <= 0 ) throw new LuaException( "Colour out of range" );
|
||||
colour = getHighestBit( colour ) - 1;
|
||||
if( colour < 0 || colour > 15 ) throw new LuaException( "Colour out of range" );
|
||||
return colour;
|
||||
}
|
||||
|
||||
|
||||
public static int encodeColour( int colour )
|
||||
{
|
||||
return 1 << colour;
|
||||
}
|
||||
|
||||
public static void setColour( Terminal terminal, int colour, double r, double g, double b )
|
||||
{
|
||||
if( terminal.getPalette() != null )
|
||||
{
|
||||
terminal.getPalette().setColour( colour, r, g, b );
|
||||
terminal.setChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,10 @@
|
||||
*/
|
||||
package dan200.computercraft.core.apis.handles;
|
||||
|
||||
import com.google.common.collect.ObjectArrays;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.IArguments;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
@@ -17,208 +16,205 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static dan200.computercraft.api.lua.ArgumentHelper.getInt;
|
||||
import static dan200.computercraft.api.lua.ArgumentHelper.optBoolean;
|
||||
import java.util.Optional;
|
||||
|
||||
public class BinaryReadableHandle extends HandleGeneric
|
||||
{
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
|
||||
private static final String[] METHOD_NAMES = new String[] { "read", "readAll", "readLine", "close" };
|
||||
private static final String[] METHOD_SEEK_NAMES = ObjectArrays.concat( METHOD_NAMES, new String[] { "seek" }, String.class );
|
||||
|
||||
private final ReadableByteChannel m_reader;
|
||||
private final SeekableByteChannel m_seekable;
|
||||
private final ReadableByteChannel reader;
|
||||
final SeekableByteChannel seekable;
|
||||
private final ByteBuffer single = ByteBuffer.allocate( 1 );
|
||||
|
||||
public BinaryReadableHandle( ReadableByteChannel channel, Closeable closeable )
|
||||
protected BinaryReadableHandle( ReadableByteChannel reader, SeekableByteChannel seekable, Closeable closeable )
|
||||
{
|
||||
super( closeable );
|
||||
m_reader = channel;
|
||||
m_seekable = asSeekable( channel );
|
||||
this.reader = reader;
|
||||
this.seekable = seekable;
|
||||
}
|
||||
|
||||
public BinaryReadableHandle( ReadableByteChannel channel )
|
||||
public static BinaryReadableHandle of( ReadableByteChannel channel, Closeable closeable )
|
||||
{
|
||||
this( channel, channel );
|
||||
SeekableByteChannel seekable = asSeekable( channel );
|
||||
return seekable == null ? new BinaryReadableHandle( channel, null, closeable ) : new Seekable( seekable, closeable );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
public static BinaryReadableHandle of( ReadableByteChannel channel )
|
||||
{
|
||||
return m_seekable == null ? METHOD_NAMES : METHOD_SEEK_NAMES;
|
||||
return of( channel, channel );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
|
||||
@LuaFunction
|
||||
public final Object[] read( Optional<Integer> countArg ) throws LuaException
|
||||
{
|
||||
switch( method )
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
case 0: // read
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
if( args.length > 0 && args[0] != null )
|
||||
{
|
||||
int count = getInt( args, 0 );
|
||||
if( count < 0 )
|
||||
{
|
||||
throw new LuaException( "Cannot read a negative number of bytes" );
|
||||
}
|
||||
else if( count == 0 && m_seekable != null )
|
||||
{
|
||||
return m_seekable.position() >= m_seekable.size() ? null : new Object[] { "" };
|
||||
}
|
||||
|
||||
if( count <= BUFFER_SIZE )
|
||||
{
|
||||
ByteBuffer buffer = ByteBuffer.allocate( count );
|
||||
|
||||
int read = m_reader.read( buffer );
|
||||
if( read < 0 ) return null;
|
||||
return new Object[] { read < count ? Arrays.copyOf( buffer.array(), read ) : buffer.array() };
|
||||
}
|
||||
else
|
||||
{
|
||||
// Read the initial set of characters, failing if none are read.
|
||||
ByteBuffer buffer = ByteBuffer.allocate( BUFFER_SIZE );
|
||||
int read = m_reader.read( buffer );
|
||||
if( read < 0 ) return null;
|
||||
|
||||
// If we failed to read "enough" here, let's just abort
|
||||
if( read >= count || read < BUFFER_SIZE )
|
||||
{
|
||||
return new Object[] { Arrays.copyOf( buffer.array(), read ) };
|
||||
}
|
||||
|
||||
// Build up an array of ByteBuffers. Hopefully this means we can perform less allocation
|
||||
// than doubling up the buffer each time.
|
||||
int totalRead = read;
|
||||
List<ByteBuffer> parts = new ArrayList<>( 4 );
|
||||
parts.add( buffer );
|
||||
while( read >= BUFFER_SIZE && totalRead < count )
|
||||
{
|
||||
buffer = ByteBuffer.allocate( Math.min( BUFFER_SIZE, count - totalRead ) );
|
||||
read = m_reader.read( buffer );
|
||||
if( read < 0 ) break;
|
||||
|
||||
totalRead += read;
|
||||
parts.add( buffer );
|
||||
}
|
||||
|
||||
// Now just copy all the bytes across!
|
||||
byte[] bytes = new byte[totalRead];
|
||||
int pos = 0;
|
||||
for( ByteBuffer part : parts )
|
||||
{
|
||||
System.arraycopy( part.array(), 0, bytes, pos, part.position() );
|
||||
pos += part.position();
|
||||
}
|
||||
return new Object[] { bytes };
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
single.clear();
|
||||
int b = m_reader.read( single );
|
||||
return b == -1 ? null : new Object[] { single.get( 0 ) & 0xFF };
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
case 1: // readAll
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
int expected = 32;
|
||||
if( m_seekable != null )
|
||||
{
|
||||
expected = Math.max( expected, (int) (m_seekable.size() - m_seekable.position()) );
|
||||
}
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream( expected );
|
||||
|
||||
ByteBuffer buf = ByteBuffer.allocate( 8192 );
|
||||
boolean readAnything = false;
|
||||
while( true )
|
||||
{
|
||||
buf.clear();
|
||||
int r = m_reader.read( buf );
|
||||
if( r == -1 ) break;
|
||||
|
||||
readAnything = true;
|
||||
stream.write( buf.array(), 0, r );
|
||||
}
|
||||
return readAnything ? new Object[] { stream.toByteArray() } : null;
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
case 2: // readLine
|
||||
if( countArg.isPresent() )
|
||||
{
|
||||
checkOpen();
|
||||
boolean withTrailing = optBoolean( args, 0, false );
|
||||
try
|
||||
int count = countArg.get();
|
||||
if( count < 0 ) throw new LuaException( "Cannot read a negative number of bytes" );
|
||||
if( count == 0 && seekable != null )
|
||||
{
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
|
||||
boolean readAnything = false, readRc = false;
|
||||
while( true )
|
||||
{
|
||||
single.clear();
|
||||
int read = m_reader.read( single );
|
||||
if( read <= 0 )
|
||||
{
|
||||
// Nothing else to read, and we saw no \n. Return the array. If we saw a \r, then add it
|
||||
// back.
|
||||
if( readRc ) stream.write( '\r' );
|
||||
return readAnything ? new Object[] { stream.toByteArray() } : null;
|
||||
}
|
||||
|
||||
readAnything = true;
|
||||
|
||||
byte chr = single.get( 0 );
|
||||
if( chr == '\n' )
|
||||
{
|
||||
if( withTrailing )
|
||||
{
|
||||
if( readRc ) stream.write( '\r' );
|
||||
stream.write( chr );
|
||||
}
|
||||
return new Object[] { stream.toByteArray() };
|
||||
}
|
||||
else
|
||||
{
|
||||
// We want to skip \r\n, but obviously need to include cases where \r is not followed by \n.
|
||||
// Note, this behaviour is non-standard compliant (strictly speaking we should have no
|
||||
// special logic for \r), but we preserve compatibility with EncodedReadableHandle and
|
||||
// previous behaviour of the io library.
|
||||
if( readRc ) stream.write( '\r' );
|
||||
readRc = chr == '\r';
|
||||
if( !readRc ) stream.write( chr );
|
||||
}
|
||||
}
|
||||
return seekable.position() >= seekable.size() ? null : new Object[] { "" };
|
||||
}
|
||||
catch( IOException e )
|
||||
|
||||
if( count <= BUFFER_SIZE )
|
||||
{
|
||||
return null;
|
||||
ByteBuffer buffer = ByteBuffer.allocate( count );
|
||||
|
||||
int read = reader.read( buffer );
|
||||
if( read < 0 ) return null;
|
||||
buffer.flip();
|
||||
return new Object[] { buffer };
|
||||
}
|
||||
else
|
||||
{
|
||||
// Read the initial set of characters, failing if none are read.
|
||||
ByteBuffer buffer = ByteBuffer.allocate( BUFFER_SIZE );
|
||||
int read = reader.read( buffer );
|
||||
if( read < 0 ) return null;
|
||||
|
||||
// If we failed to read "enough" here, let's just abort
|
||||
if( read >= count || read < BUFFER_SIZE )
|
||||
{
|
||||
buffer.flip();
|
||||
return new Object[] { buffer };
|
||||
}
|
||||
|
||||
// Build up an array of ByteBuffers. Hopefully this means we can perform less allocation
|
||||
// than doubling up the buffer each time.
|
||||
int totalRead = read;
|
||||
List<ByteBuffer> parts = new ArrayList<>( 4 );
|
||||
parts.add( buffer );
|
||||
while( read >= BUFFER_SIZE && totalRead < count )
|
||||
{
|
||||
buffer = ByteBuffer.allocate( Math.min( BUFFER_SIZE, count - totalRead ) );
|
||||
read = reader.read( buffer );
|
||||
if( read < 0 ) break;
|
||||
|
||||
totalRead += read;
|
||||
parts.add( buffer );
|
||||
}
|
||||
|
||||
// Now just copy all the bytes across!
|
||||
byte[] bytes = new byte[totalRead];
|
||||
int pos = 0;
|
||||
for( ByteBuffer part : parts )
|
||||
{
|
||||
System.arraycopy( part.array(), 0, bytes, pos, part.position() );
|
||||
pos += part.position();
|
||||
}
|
||||
return new Object[] { bytes };
|
||||
}
|
||||
}
|
||||
case 3: // close
|
||||
checkOpen();
|
||||
close();
|
||||
return null;
|
||||
case 4: // seek
|
||||
checkOpen();
|
||||
return handleSeek( m_seekable, args );
|
||||
default:
|
||||
return null;
|
||||
else
|
||||
{
|
||||
single.clear();
|
||||
int b = reader.read( single );
|
||||
return b == -1 ? null : new Object[] { single.get( 0 ) & 0xFF };
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final Object[] readAll() throws LuaException
|
||||
{
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
int expected = 32;
|
||||
if( seekable != null ) expected = Math.max( expected, (int) (seekable.size() - seekable.position()) );
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream( expected );
|
||||
|
||||
ByteBuffer buf = ByteBuffer.allocate( 8192 );
|
||||
boolean readAnything = false;
|
||||
while( true )
|
||||
{
|
||||
buf.clear();
|
||||
int r = reader.read( buf );
|
||||
if( r == -1 ) break;
|
||||
|
||||
readAnything = true;
|
||||
stream.write( buf.array(), 0, r );
|
||||
}
|
||||
return readAnything ? new Object[] { stream.toByteArray() } : null;
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final Object[] readLine( Optional<Boolean> withTrailingArg ) throws LuaException
|
||||
{
|
||||
checkOpen();
|
||||
boolean withTrailing = withTrailingArg.orElse( false );
|
||||
try
|
||||
{
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
|
||||
boolean readAnything = false, readRc = false;
|
||||
while( true )
|
||||
{
|
||||
single.clear();
|
||||
int read = reader.read( single );
|
||||
if( read <= 0 )
|
||||
{
|
||||
// Nothing else to read, and we saw no \n. Return the array. If we saw a \r, then add it
|
||||
// back.
|
||||
if( readRc ) stream.write( '\r' );
|
||||
return readAnything ? new Object[] { stream.toByteArray() } : null;
|
||||
}
|
||||
|
||||
readAnything = true;
|
||||
|
||||
byte chr = single.get( 0 );
|
||||
if( chr == '\n' )
|
||||
{
|
||||
if( withTrailing )
|
||||
{
|
||||
if( readRc ) stream.write( '\r' );
|
||||
stream.write( chr );
|
||||
}
|
||||
return new Object[] { stream.toByteArray() };
|
||||
}
|
||||
else
|
||||
{
|
||||
// We want to skip \r\n, but obviously need to include cases where \r is not followed by \n.
|
||||
// Note, this behaviour is non-standard compliant (strictly speaking we should have no
|
||||
// special logic for \r), but we preserve compatibility with EncodedReadableHandle and
|
||||
// previous behaviour of the io library.
|
||||
if( readRc ) stream.write( '\r' );
|
||||
readRc = chr == '\r';
|
||||
if( !readRc ) stream.write( chr );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Seekable extends BinaryReadableHandle
|
||||
{
|
||||
public Seekable( SeekableByteChannel seekable, Closeable closeable )
|
||||
{
|
||||
super( seekable, seekable, closeable );
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final Object[] seek( IArguments arguments ) throws LuaException
|
||||
{
|
||||
checkOpen();
|
||||
return handleSeek( seekable, arguments );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,11 @@
|
||||
*/
|
||||
package dan200.computercraft.core.apis.handles;
|
||||
|
||||
import com.google.common.collect.ObjectArrays;
|
||||
import dan200.computercraft.api.lua.ArgumentHelper;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.IArguments;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.shared.util.StringUtil;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.api.lua.LuaValues;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -21,87 +19,85 @@ import java.nio.channels.WritableByteChannel;
|
||||
|
||||
public class BinaryWritableHandle extends HandleGeneric
|
||||
{
|
||||
private static final String[] METHOD_NAMES = new String[] { "write", "flush", "close" };
|
||||
private static final String[] METHOD_SEEK_NAMES = ObjectArrays.concat( METHOD_NAMES, new String[] { "seek" }, String.class );
|
||||
|
||||
private final WritableByteChannel m_writer;
|
||||
private final SeekableByteChannel m_seekable;
|
||||
private final WritableByteChannel writer;
|
||||
final SeekableByteChannel seekable;
|
||||
private final ByteBuffer single = ByteBuffer.allocate( 1 );
|
||||
|
||||
public BinaryWritableHandle( WritableByteChannel channel, Closeable closeable )
|
||||
protected BinaryWritableHandle( WritableByteChannel writer, SeekableByteChannel seekable, Closeable closeable )
|
||||
{
|
||||
super( closeable );
|
||||
m_writer = channel;
|
||||
m_seekable = asSeekable( channel );
|
||||
this.writer = writer;
|
||||
this.seekable = seekable;
|
||||
}
|
||||
|
||||
public BinaryWritableHandle( WritableByteChannel channel )
|
||||
public static BinaryWritableHandle of( WritableByteChannel channel, Closeable closeable )
|
||||
{
|
||||
this( channel, channel );
|
||||
SeekableByteChannel seekable = asSeekable( channel );
|
||||
return seekable == null ? new BinaryWritableHandle( channel, null, closeable ) : new Seekable( seekable, closeable );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
public static BinaryWritableHandle of( WritableByteChannel channel )
|
||||
{
|
||||
return m_seekable == null ? METHOD_NAMES : METHOD_SEEK_NAMES;
|
||||
return of( channel, channel );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
|
||||
@LuaFunction
|
||||
public final void write( IArguments arguments ) throws LuaException
|
||||
{
|
||||
switch( method )
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
case 0: // write
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
if( args.length > 0 && args[0] instanceof Number )
|
||||
{
|
||||
int number = ((Number) args[0]).intValue();
|
||||
single.clear();
|
||||
single.put( (byte) number );
|
||||
single.flip();
|
||||
Object arg = arguments.get( 0 );
|
||||
if( arg instanceof Number )
|
||||
{
|
||||
int number = ((Number) arg).intValue();
|
||||
single.clear();
|
||||
single.put( (byte) number );
|
||||
single.flip();
|
||||
|
||||
m_writer.write( single );
|
||||
}
|
||||
else if( args.length > 0 && args[0] instanceof String )
|
||||
{
|
||||
String value = (String) args[0];
|
||||
m_writer.write( ByteBuffer.wrap( StringUtil.encodeString( value ) ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw ArgumentHelper.badArgumentOf( 0, "string or number", args.length > 0 ? args[0] : null );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
case 1: // flush
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
// Technically this is not needed
|
||||
if( m_writer instanceof FileChannel ) ((FileChannel) m_writer).force( false );
|
||||
writer.write( single );
|
||||
}
|
||||
else if( arg instanceof String )
|
||||
{
|
||||
writer.write( arguments.getBytes( 0 ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw LuaValues.badArgumentOf( 0, "string or number", arg );
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
case 2: // close
|
||||
checkOpen();
|
||||
close();
|
||||
return null;
|
||||
case 3: // seek
|
||||
checkOpen();
|
||||
return handleSeek( m_seekable, args );
|
||||
default:
|
||||
return null;
|
||||
@LuaFunction
|
||||
public final void flush() throws LuaException
|
||||
{
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
// Technically this is not needed
|
||||
if( writer instanceof FileChannel ) ((FileChannel) writer).force( false );
|
||||
}
|
||||
catch( IOException ignored )
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public static class Seekable extends BinaryWritableHandle
|
||||
{
|
||||
public Seekable( SeekableByteChannel seekable, Closeable closeable )
|
||||
{
|
||||
super( seekable, seekable, closeable );
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final Object[] seek( IArguments args ) throws LuaException
|
||||
{
|
||||
checkOpen();
|
||||
return handleSeek( seekable, args );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
*/
|
||||
package dan200.computercraft.core.apis.handles;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.BufferedReader;
|
||||
@@ -18,20 +18,18 @@ import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static dan200.computercraft.api.lua.ArgumentHelper.optBoolean;
|
||||
import static dan200.computercraft.api.lua.ArgumentHelper.optInt;
|
||||
import java.util.Optional;
|
||||
|
||||
public class EncodedReadableHandle extends HandleGeneric
|
||||
{
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
|
||||
private BufferedReader m_reader;
|
||||
private final BufferedReader reader;
|
||||
|
||||
public EncodedReadableHandle( @Nonnull BufferedReader reader, @Nonnull Closeable closable )
|
||||
{
|
||||
super( closable );
|
||||
m_reader = reader;
|
||||
this.reader = reader;
|
||||
}
|
||||
|
||||
public EncodedReadableHandle( @Nonnull BufferedReader reader )
|
||||
@@ -39,123 +37,107 @@ public class EncodedReadableHandle extends HandleGeneric
|
||||
this( reader, reader );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
@LuaFunction
|
||||
public final Object[] readLine( Optional<Boolean> withTrailingArg ) throws LuaException
|
||||
{
|
||||
return new String[] {
|
||||
"readLine",
|
||||
"readAll",
|
||||
"read",
|
||||
"close",
|
||||
};
|
||||
checkOpen();
|
||||
boolean withTrailing = withTrailingArg.orElse( false );
|
||||
try
|
||||
{
|
||||
String line = reader.readLine();
|
||||
if( line != null )
|
||||
{
|
||||
// While this is technically inaccurate, it's better than nothing
|
||||
if( withTrailing ) line += "\n";
|
||||
return new Object[] { line };
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
|
||||
@LuaFunction
|
||||
public final Object[] readAll() throws LuaException
|
||||
{
|
||||
switch( method )
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
case 0: // readLine
|
||||
StringBuilder result = new StringBuilder();
|
||||
String line = reader.readLine();
|
||||
while( line != null )
|
||||
{
|
||||
checkOpen();
|
||||
boolean withTrailing = optBoolean( args, 0, false );
|
||||
try
|
||||
result.append( line );
|
||||
line = reader.readLine();
|
||||
if( line != null )
|
||||
{
|
||||
String line = m_reader.readLine();
|
||||
if( line != null )
|
||||
{
|
||||
// While this is technically inaccurate, it's better than nothing
|
||||
if( withTrailing ) line += "\n";
|
||||
return new Object[] { line };
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
result.append( "\n" );
|
||||
}
|
||||
}
|
||||
case 1: // readAll
|
||||
checkOpen();
|
||||
try
|
||||
return new Object[] { result.toString() };
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final Object[] read( Optional<Integer> countA ) throws LuaException
|
||||
{
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
int count = countA.orElse( 1 );
|
||||
if( count < 0 )
|
||||
{
|
||||
// Whilst this may seem absurd to allow reading 0 characters, PUC Lua it so
|
||||
// it seems best to remain somewhat consistent.
|
||||
throw new LuaException( "Cannot read a negative number of characters" );
|
||||
}
|
||||
else if( count <= BUFFER_SIZE )
|
||||
{
|
||||
// If we've got a small count, then allocate that and read it.
|
||||
char[] chars = new char[count];
|
||||
int read = reader.read( chars );
|
||||
|
||||
return read < 0 ? null : new Object[] { new String( chars, 0, read ) };
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we've got a large count, read in bunches of 8192.
|
||||
char[] buffer = new char[BUFFER_SIZE];
|
||||
|
||||
// Read the initial set of characters, failing if none are read.
|
||||
int read = reader.read( buffer, 0, Math.min( buffer.length, count ) );
|
||||
if( read < 0 ) return null;
|
||||
|
||||
StringBuilder out = new StringBuilder( read );
|
||||
int totalRead = read;
|
||||
out.append( buffer, 0, read );
|
||||
|
||||
// Otherwise read until we either reach the limit or we no longer consume
|
||||
// the full buffer.
|
||||
while( read >= BUFFER_SIZE && totalRead < count )
|
||||
{
|
||||
StringBuilder result = new StringBuilder();
|
||||
String line = m_reader.readLine();
|
||||
while( line != null )
|
||||
{
|
||||
result.append( line );
|
||||
line = m_reader.readLine();
|
||||
if( line != null )
|
||||
{
|
||||
result.append( "\n" );
|
||||
}
|
||||
}
|
||||
return new Object[] { result.toString() };
|
||||
read = reader.read( buffer, 0, Math.min( BUFFER_SIZE, count - totalRead ) );
|
||||
if( read < 0 ) break;
|
||||
|
||||
totalRead += read;
|
||||
out.append( buffer, 0, read );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
case 2: // read
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
int count = optInt( args, 0, 1 );
|
||||
if( count < 0 )
|
||||
{
|
||||
// Whilst this may seem absurd to allow reading 0 characters, PUC Lua it so
|
||||
// it seems best to remain somewhat consistent.
|
||||
throw new LuaException( "Cannot read a negative number of characters" );
|
||||
}
|
||||
else if( count <= BUFFER_SIZE )
|
||||
{
|
||||
// If we've got a small count, then allocate that and read it.
|
||||
char[] chars = new char[count];
|
||||
int read = m_reader.read( chars );
|
||||
|
||||
return read < 0 ? null : new Object[] { new String( chars, 0, read ) };
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we've got a large count, read in bunches of 8192.
|
||||
char[] buffer = new char[BUFFER_SIZE];
|
||||
|
||||
// Read the initial set of characters, failing if none are read.
|
||||
int read = m_reader.read( buffer, 0, Math.min( buffer.length, count ) );
|
||||
if( read < 0 ) return null;
|
||||
|
||||
StringBuilder out = new StringBuilder( read );
|
||||
int totalRead = read;
|
||||
out.append( buffer, 0, read );
|
||||
|
||||
// Otherwise read until we either reach the limit or we no longer consume
|
||||
// the full buffer.
|
||||
while( read >= BUFFER_SIZE && totalRead < count )
|
||||
{
|
||||
read = m_reader.read( buffer, 0, Math.min( BUFFER_SIZE, count - totalRead ) );
|
||||
if( read < 0 ) break;
|
||||
|
||||
totalRead += read;
|
||||
out.append( buffer, 0, read );
|
||||
}
|
||||
|
||||
return new Object[] { out.toString() };
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
case 3: // close
|
||||
checkOpen();
|
||||
close();
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
return new Object[] { out.toString() };
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
*/
|
||||
package dan200.computercraft.core.apis.handles;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.IArguments;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.shared.util.StringUtil;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.BufferedWriter;
|
||||
@@ -21,85 +23,57 @@ import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class EncodedWritableHandle extends HandleGeneric
|
||||
{
|
||||
private BufferedWriter m_writer;
|
||||
private final BufferedWriter writer;
|
||||
|
||||
public EncodedWritableHandle( @Nonnull BufferedWriter writer, @Nonnull Closeable closable )
|
||||
{
|
||||
super( closable );
|
||||
m_writer = writer;
|
||||
this.writer = writer;
|
||||
}
|
||||
|
||||
public EncodedWritableHandle( @Nonnull BufferedWriter writer )
|
||||
@LuaFunction
|
||||
public final void write( IArguments args ) throws LuaException
|
||||
{
|
||||
this( writer, writer );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
{
|
||||
return new String[] {
|
||||
"write",
|
||||
"writeLine",
|
||||
"flush",
|
||||
"close",
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
|
||||
{
|
||||
switch( method )
|
||||
checkOpen();
|
||||
String text = StringUtil.toString( args.get( 0 ) );
|
||||
try
|
||||
{
|
||||
case 0: // write
|
||||
{
|
||||
checkOpen();
|
||||
String text = args.length > 0 && args[0] != null ? args[0].toString() : "";
|
||||
try
|
||||
{
|
||||
m_writer.write( text, 0, text.length() );
|
||||
return null;
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
case 1: // writeLine
|
||||
{
|
||||
checkOpen();
|
||||
String text = args.length > 0 && args[0] != null ? args[0].toString() : "";
|
||||
try
|
||||
{
|
||||
m_writer.write( text, 0, text.length() );
|
||||
m_writer.newLine();
|
||||
return null;
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
case 2: // flush
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
m_writer.flush();
|
||||
return null;
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
case 3: // close
|
||||
checkOpen();
|
||||
close();
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
writer.write( text, 0, text.length() );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final void writeLine( IArguments args ) throws LuaException
|
||||
{
|
||||
checkOpen();
|
||||
String text = StringUtil.toString( args.get( 0 ) );
|
||||
try
|
||||
{
|
||||
writer.write( text, 0, text.length() );
|
||||
writer.newLine();
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final void flush() throws LuaException
|
||||
{
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
writer.flush();
|
||||
}
|
||||
catch( IOException ignored )
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public static BufferedWriter openUtf8( WritableByteChannel channel )
|
||||
{
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
*/
|
||||
package dan200.computercraft.core.apis.handles;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaObject;
|
||||
import dan200.computercraft.api.lua.IArguments;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.shared.util.IoUtil;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
@@ -15,36 +16,41 @@ import java.io.IOException;
|
||||
import java.nio.channels.Channel;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
|
||||
import static dan200.computercraft.api.lua.ArgumentHelper.optLong;
|
||||
import static dan200.computercraft.api.lua.ArgumentHelper.optString;
|
||||
|
||||
public abstract class HandleGeneric implements ILuaObject
|
||||
public abstract class HandleGeneric
|
||||
{
|
||||
private Closeable m_closable;
|
||||
private boolean m_open = true;
|
||||
private Closeable closable;
|
||||
private boolean open = true;
|
||||
|
||||
protected HandleGeneric( @Nonnull Closeable closable )
|
||||
{
|
||||
m_closable = closable;
|
||||
this.closable = closable;
|
||||
}
|
||||
|
||||
protected void checkOpen() throws LuaException
|
||||
{
|
||||
if( !m_open ) throw new LuaException( "attempt to use a closed file" );
|
||||
if( !open ) throw new LuaException( "attempt to use a closed file" );
|
||||
}
|
||||
|
||||
protected final void close()
|
||||
{
|
||||
m_open = false;
|
||||
open = false;
|
||||
|
||||
Closeable closeable = m_closable;
|
||||
Closeable closeable = closable;
|
||||
if( closeable != null )
|
||||
{
|
||||
IoUtil.closeQuietly( closeable );
|
||||
m_closable = null;
|
||||
closable = null;
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction( "close" )
|
||||
public final void doClose() throws LuaException
|
||||
{
|
||||
checkOpen();
|
||||
close();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shared implementation for various file handle types.
|
||||
*
|
||||
@@ -54,12 +60,12 @@ public abstract class HandleGeneric implements ILuaObject
|
||||
* @throws LuaException If the arguments were invalid
|
||||
* @see <a href="https://www.lua.org/manual/5.1/manual.html#pdf-file:seek">{@code file:seek} in the Lua manual.</a>
|
||||
*/
|
||||
protected static Object[] handleSeek( SeekableByteChannel channel, Object[] args ) throws LuaException
|
||||
protected static Object[] handleSeek( SeekableByteChannel channel, IArguments args ) throws LuaException
|
||||
{
|
||||
String whence = args.optString( 0, "cur" );
|
||||
long offset = args.optLong( 1, 0 );
|
||||
try
|
||||
{
|
||||
String whence = optString( args, 0, "cur" );
|
||||
long offset = optLong( args, 1, 0 );
|
||||
switch( whence )
|
||||
{
|
||||
case "set":
|
||||
|
||||
@@ -7,6 +7,7 @@ package dan200.computercraft.core.apis.http;
|
||||
|
||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
@@ -46,12 +47,14 @@ public class CheckUrl extends Resource<CheckUrl>
|
||||
|
||||
try
|
||||
{
|
||||
NetworkUtils.getAddress( host, 80, false );
|
||||
if( tryClose() ) environment.queueEvent( EVENT, new Object[] { address, true } );
|
||||
InetSocketAddress netAddress = NetworkUtils.getAddress( host, 80, false );
|
||||
NetworkUtils.getOptions( host, netAddress );
|
||||
|
||||
if( tryClose() ) environment.queueEvent( EVENT, address, true );
|
||||
}
|
||||
catch( HTTPRequestException e )
|
||||
{
|
||||
if( tryClose() ) environment.queueEvent( EVENT, new Object[] { address, false, e.getMessage() } );
|
||||
if( tryClose() ) environment.queueEvent( EVENT, address, false, e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
package dan200.computercraft.core.apis.http;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.core.apis.http.options.Action;
|
||||
import dan200.computercraft.core.apis.http.options.AddressRule;
|
||||
import dan200.computercraft.core.apis.http.options.Options;
|
||||
import dan200.computercraft.shared.util.ThreadUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
@@ -15,7 +18,6 @@ import io.netty.handler.ssl.SslContextBuilder;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.KeyStore;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
@@ -106,24 +108,31 @@ public final class NetworkUtils
|
||||
* @param port The port, or -1 if not defined.
|
||||
* @param ssl Whether to connect with SSL. This is used to find the default port if not otherwise specified.
|
||||
* @return The resolved address.
|
||||
* @throws HTTPRequestException If the host is not permitted.
|
||||
* @throws HTTPRequestException If the host is not malformed.
|
||||
*/
|
||||
public static InetSocketAddress getAddress( String host, int port, boolean ssl ) throws HTTPRequestException
|
||||
{
|
||||
if( port < 0 ) port = ssl ? 443 : 80;
|
||||
|
||||
InetSocketAddress socketAddress = new InetSocketAddress( host, port );
|
||||
if( socketAddress.isUnresolved() ) throw new HTTPRequestException( "Unknown host" );
|
||||
|
||||
InetAddress address = socketAddress.getAddress();
|
||||
if( AddressRule.apply( ComputerCraft.httpRules, host, address ) == AddressRule.Action.DENY )
|
||||
{
|
||||
throw new HTTPRequestException( "Domain not permitted" );
|
||||
}
|
||||
|
||||
return socketAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get options for a specific domain.
|
||||
*
|
||||
* @param host The host to resolve.
|
||||
* @param address The address, resolved by {@link #getAddress(String, int, boolean)}.
|
||||
* @return The options for this host.
|
||||
* @throws HTTPRequestException If the host is not permitted
|
||||
*/
|
||||
public static Options getOptions( String host, InetSocketAddress address ) throws HTTPRequestException
|
||||
{
|
||||
Options options = AddressRule.apply( ComputerCraft.httpRules, host, address.getAddress() );
|
||||
if( options.action == Action.DENY ) throw new HTTPRequestException( "Domain not permitted" );
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a {@link ByteBuf} into a byte array.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.apis.http.options;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public enum Action
|
||||
{
|
||||
ALLOW,
|
||||
DENY;
|
||||
|
||||
private final PartialOptions partial = new PartialOptions( this, null, null, null, null );
|
||||
|
||||
@Nonnull
|
||||
public PartialOptions toPartial()
|
||||
{
|
||||
return partial;
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,12 @@
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
package dan200.computercraft.core.apis.http;
|
||||
package dan200.computercraft.core.apis.http.options;
|
||||
|
||||
import com.google.common.net.InetAddresses;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
@@ -18,6 +19,11 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
public final class AddressRule
|
||||
{
|
||||
public static final long MAX_DOWNLOAD = 16 * 1024 * 1024;
|
||||
public static final long MAX_UPLOAD = 4 * 1024 * 1024;
|
||||
public static final int TIMEOUT = 30_000;
|
||||
public static final int WEBSOCKET_MESSAGE = 128 * 1024;
|
||||
|
||||
private static final class HostRange
|
||||
{
|
||||
private final byte[] min;
|
||||
@@ -44,25 +50,19 @@ public final class AddressRule
|
||||
}
|
||||
}
|
||||
|
||||
public enum Action
|
||||
{
|
||||
ALLOW,
|
||||
DENY,
|
||||
}
|
||||
|
||||
private final HostRange ip;
|
||||
private final Pattern domainPattern;
|
||||
private final Action action;
|
||||
private final PartialOptions partial;
|
||||
|
||||
private AddressRule( HostRange ip, Pattern domainPattern, Action action )
|
||||
private AddressRule( @Nullable HostRange ip, @Nullable Pattern domainPattern, @Nonnull PartialOptions partial )
|
||||
{
|
||||
this.ip = ip;
|
||||
this.domainPattern = domainPattern;
|
||||
this.action = action;
|
||||
this.partial = partial;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static AddressRule parse( String filter, Action action )
|
||||
public static AddressRule parse( String filter, @Nonnull PartialOptions partial )
|
||||
{
|
||||
int cidr = filter.indexOf( '/' );
|
||||
if( cidr >= 0 )
|
||||
@@ -117,12 +117,12 @@ public final class AddressRule
|
||||
size -= 8;
|
||||
}
|
||||
|
||||
return new AddressRule( new HostRange( minBytes, maxBytes ), null, action );
|
||||
return new AddressRule( new HostRange( minBytes, maxBytes ), null, partial );
|
||||
}
|
||||
else
|
||||
{
|
||||
Pattern pattern = Pattern.compile( "^\\Q" + filter.replaceAll( "\\*", "\\\\E.*\\\\Q" ) + "\\E$" );
|
||||
return new AddressRule( null, pattern, action );
|
||||
return new AddressRule( null, pattern, partial );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ public final class AddressRule
|
||||
* @param address The address to check.
|
||||
* @return Whether it matches any of these patterns.
|
||||
*/
|
||||
public boolean matches( String domain, InetAddress address )
|
||||
private boolean matches( String domain, InetAddress address )
|
||||
{
|
||||
if( domainPattern != null )
|
||||
{
|
||||
@@ -155,13 +155,32 @@ public final class AddressRule
|
||||
return ip != null && ip.contains( address );
|
||||
}
|
||||
|
||||
public static Action apply( Iterable<? extends AddressRule> rules, String domain, InetAddress address )
|
||||
public static Options apply( Iterable<? extends AddressRule> rules, String domain, InetAddress address )
|
||||
{
|
||||
PartialOptions options = null;
|
||||
boolean hasMany = false;
|
||||
|
||||
for( AddressRule rule : rules )
|
||||
{
|
||||
if( rule.matches( domain, address ) ) return rule.action;
|
||||
if( !rule.matches( domain, address ) ) continue;
|
||||
|
||||
if( options == null )
|
||||
{
|
||||
options = rule.partial;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if( !hasMany )
|
||||
{
|
||||
options = options.copy();
|
||||
hasMany = true;
|
||||
}
|
||||
|
||||
options.merge( rule.partial );
|
||||
}
|
||||
}
|
||||
|
||||
return Action.DENY;
|
||||
return (options == null ? PartialOptions.DEFAULT : options).toOptions();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.apis.http.options;
|
||||
|
||||
import com.electronwill.nightconfig.core.CommentedConfig;
|
||||
import com.electronwill.nightconfig.core.Config;
|
||||
import com.electronwill.nightconfig.core.InMemoryCommentedFormat;
|
||||
import com.electronwill.nightconfig.core.UnmodifiableConfig;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Parses, checks and generates {@link Config}s for {@link AddressRule}.
|
||||
*/
|
||||
public class AddressRuleConfig
|
||||
{
|
||||
public static UnmodifiableConfig makeRule( String host, Action action )
|
||||
{
|
||||
CommentedConfig config = InMemoryCommentedFormat.defaultInstance().createConfig( ConcurrentHashMap::new );
|
||||
config.add( "host", host );
|
||||
config.add( "action", action.name().toLowerCase( Locale.ROOT ) );
|
||||
|
||||
if( host.equals( "*" ) && action == Action.ALLOW )
|
||||
{
|
||||
config.setComment( "timeout", "The period of time (in milliseconds) to wait before a HTTP request times out. Set to 0 for unlimited." );
|
||||
config.add( "timeout", AddressRule.TIMEOUT );
|
||||
|
||||
config.setComment( "max_download", "The maximum size (in bytes) that a computer can download in a single request. Note that responses may receive more data than allowed, but this data will not be returned to the client." );
|
||||
config.set( "max_download", AddressRule.MAX_DOWNLOAD );
|
||||
|
||||
config.setComment( "max_upload", "The maximum size (in bytes) that a computer can upload in a single request. This includes headers and POST text." );
|
||||
config.set( "max_upload", AddressRule.MAX_UPLOAD );
|
||||
|
||||
config.setComment( "max_websocket_message", "The maximum size (in bytes) that a computer can send or receive in one websocket packet." );
|
||||
config.set( "max_websocket_message", AddressRule.WEBSOCKET_MESSAGE );
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
public static boolean checkRule( UnmodifiableConfig builder )
|
||||
{
|
||||
String hostObj = get( builder, "host", String.class ).orElse( null );
|
||||
return hostObj != null && checkEnum( builder, "action", Action.class )
|
||||
&& check( builder, "timeout", Number.class )
|
||||
&& check( builder, "max_upload", Number.class )
|
||||
&& check( builder, "max_download", Number.class )
|
||||
&& check( builder, "websocket_message", Number.class )
|
||||
&& AddressRule.parse( hostObj, PartialOptions.DEFAULT ) != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static AddressRule parseRule( UnmodifiableConfig builder )
|
||||
{
|
||||
String hostObj = get( builder, "host", String.class ).orElse( null );
|
||||
if( hostObj == null ) return null;
|
||||
|
||||
Action action = getEnum( builder, "action", Action.class ).orElse( null );
|
||||
Integer timeout = get( builder, "timeout", Number.class ).map( Number::intValue ).orElse( null );
|
||||
Long maxUpload = get( builder, "max_upload", Number.class ).map( Number::longValue ).orElse( null );
|
||||
Long maxDownload = get( builder, "max_download", Number.class ).map( Number::longValue ).orElse( null );
|
||||
Integer websocketMessage = get( builder, "websocket_message", Number.class ).map( Number::intValue ).orElse( null );
|
||||
|
||||
PartialOptions options = new PartialOptions(
|
||||
action,
|
||||
maxUpload,
|
||||
maxDownload,
|
||||
timeout,
|
||||
websocketMessage
|
||||
);
|
||||
|
||||
return AddressRule.parse( hostObj, options );
|
||||
}
|
||||
|
||||
private static <T> boolean check( UnmodifiableConfig config, String field, Class<T> klass )
|
||||
{
|
||||
Object value = config.get( field );
|
||||
if( value == null || klass.isInstance( value ) ) return true;
|
||||
|
||||
ComputerCraft.log.warn( "HTTP rule's {} is not a {}.", field, klass.getSimpleName() );
|
||||
return false;
|
||||
}
|
||||
|
||||
private static <T extends Enum<T>> boolean checkEnum( UnmodifiableConfig config, String field, Class<T> klass )
|
||||
{
|
||||
Object value = config.get( field );
|
||||
if( value == null ) return true;
|
||||
|
||||
if( !(value instanceof String) )
|
||||
{
|
||||
ComputerCraft.log.warn( "HTTP rule's {} is not a string", field );
|
||||
return false;
|
||||
}
|
||||
|
||||
if( parseEnum( klass, (String) value ) == null )
|
||||
{
|
||||
ComputerCraft.log.warn( "HTTP rule's {} is not a known option", field );
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static <T> Optional<T> get( UnmodifiableConfig config, String field, Class<T> klass )
|
||||
{
|
||||
Object value = config.get( field );
|
||||
return klass.isInstance( value ) ? Optional.of( klass.cast( value ) ) : Optional.empty();
|
||||
}
|
||||
|
||||
private static <T extends Enum<T>> Optional<T> getEnum( UnmodifiableConfig config, String field, Class<T> klass )
|
||||
{
|
||||
return get( config, field, String.class ).map( x -> parseEnum( klass, x ) );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static <T extends Enum<T>> T parseEnum( Class<T> klass, String x )
|
||||
{
|
||||
for( T value : klass.getEnumConstants() )
|
||||
{
|
||||
if( value.name().equalsIgnoreCase( x ) ) return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.apis.http.options;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Options about a specific domain.
|
||||
*/
|
||||
public final class Options
|
||||
{
|
||||
@Nonnull
|
||||
public final Action action;
|
||||
public final long maxUpload;
|
||||
public final long maxDownload;
|
||||
public final int timeout;
|
||||
public final int websocketMessage;
|
||||
|
||||
Options( @Nonnull Action action, long maxUpload, long maxDownload, int timeout, int websocketMessage )
|
||||
{
|
||||
this.action = action;
|
||||
this.maxUpload = maxUpload;
|
||||
this.maxDownload = maxDownload;
|
||||
this.timeout = timeout;
|
||||
this.websocketMessage = websocketMessage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.apis.http.options;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public final class PartialOptions
|
||||
{
|
||||
static final PartialOptions DEFAULT = new PartialOptions( null, null, null, null, null );
|
||||
|
||||
Action action;
|
||||
Long maxUpload;
|
||||
Long maxDownload;
|
||||
Integer timeout;
|
||||
Integer websocketMessage;
|
||||
|
||||
Options options;
|
||||
|
||||
PartialOptions( Action action, Long maxUpload, Long maxDownload, Integer timeout, Integer websocketMessage )
|
||||
{
|
||||
this.action = action;
|
||||
this.maxUpload = maxUpload;
|
||||
this.maxDownload = maxDownload;
|
||||
this.timeout = timeout;
|
||||
this.websocketMessage = websocketMessage;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
Options toOptions()
|
||||
{
|
||||
if( options != null ) return options;
|
||||
|
||||
return options = new Options(
|
||||
action == null ? Action.DENY : action,
|
||||
maxUpload == null ? AddressRule.MAX_UPLOAD : maxUpload,
|
||||
maxDownload == null ? AddressRule.MAX_DOWNLOAD : maxDownload,
|
||||
timeout == null ? AddressRule.TIMEOUT : timeout,
|
||||
websocketMessage == null ? AddressRule.WEBSOCKET_MESSAGE : websocketMessage
|
||||
);
|
||||
}
|
||||
|
||||
void merge( @Nonnull PartialOptions other )
|
||||
{
|
||||
if( action == null && other.action != null ) action = other.action;
|
||||
if( maxUpload == null && other.maxUpload != null ) maxUpload = other.maxUpload;
|
||||
if( maxDownload == null && other.maxDownload != null ) maxDownload = other.maxDownload;
|
||||
if( timeout == null && other.timeout != null ) timeout = other.timeout;
|
||||
if( websocketMessage == null && other.websocketMessage != null ) websocketMessage = other.websocketMessage;
|
||||
}
|
||||
|
||||
PartialOptions copy()
|
||||
{
|
||||
return new PartialOptions( action, maxUpload, maxDownload, timeout, websocketMessage );
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,12 @@
|
||||
package dan200.computercraft.core.apis.http.request;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.ILuaObject;
|
||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||
import dan200.computercraft.core.apis.http.HTTPRequestException;
|
||||
import dan200.computercraft.core.apis.http.NetworkUtils;
|
||||
import dan200.computercraft.core.apis.http.Resource;
|
||||
import dan200.computercraft.core.apis.http.ResourceGroup;
|
||||
import dan200.computercraft.core.apis.http.options.Options;
|
||||
import dan200.computercraft.core.tracking.TrackingField;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
@@ -137,16 +137,24 @@ public class HttpRequest extends Resource<HttpRequest>
|
||||
{
|
||||
boolean ssl = uri.getScheme().equalsIgnoreCase( "https" );
|
||||
InetSocketAddress socketAddress = NetworkUtils.getAddress( uri.getHost(), uri.getPort(), ssl );
|
||||
Options options = NetworkUtils.getOptions( uri.getHost(), socketAddress );
|
||||
SslContext sslContext = ssl ? NetworkUtils.getSslContext() : null;
|
||||
|
||||
// getAddress may have a slight delay, so let's perform another cancellation check.
|
||||
if( isClosed() ) return;
|
||||
|
||||
long requestBody = getHeaderSize( headers ) + postBuffer.capacity();
|
||||
if( options.maxUpload != 0 && requestBody > options.maxUpload )
|
||||
{
|
||||
failure( "Request body is too large" );
|
||||
return;
|
||||
}
|
||||
|
||||
// Add request size to the tracker before opening the connection
|
||||
environment.addTrackingChange( TrackingField.HTTP_REQUESTS, 1 );
|
||||
environment.addTrackingChange( TrackingField.HTTP_UPLOAD, getHeaderSize( headers ) + postBuffer.capacity() );
|
||||
environment.addTrackingChange( TrackingField.HTTP_UPLOAD, requestBody );
|
||||
|
||||
HttpRequestHandler handler = currentRequest = new HttpRequestHandler( this, uri, method );
|
||||
HttpRequestHandler handler = currentRequest = new HttpRequestHandler( this, uri, method, options );
|
||||
connectFuture = new Bootstrap()
|
||||
.group( NetworkUtils.LOOP_GROUP )
|
||||
.channelFactory( NioSocketChannel::new )
|
||||
@@ -156,9 +164,9 @@ public class HttpRequest extends Resource<HttpRequest>
|
||||
protected void initChannel( SocketChannel ch )
|
||||
{
|
||||
|
||||
if( ComputerCraft.httpTimeout > 0 )
|
||||
if( options.timeout > 0 )
|
||||
{
|
||||
ch.config().setConnectTimeoutMillis( ComputerCraft.httpTimeout );
|
||||
ch.config().setConnectTimeoutMillis( options.timeout );
|
||||
}
|
||||
|
||||
ChannelPipeline p = ch.pipeline();
|
||||
@@ -167,9 +175,9 @@ public class HttpRequest extends Resource<HttpRequest>
|
||||
p.addLast( sslContext.newHandler( ch.alloc(), uri.getHost(), socketAddress.getPort() ) );
|
||||
}
|
||||
|
||||
if( ComputerCraft.httpTimeout > 0 )
|
||||
if( options.timeout > 0 )
|
||||
{
|
||||
p.addLast( new ReadTimeoutHandler( ComputerCraft.httpTimeout, TimeUnit.MILLISECONDS ) );
|
||||
p.addLast( new ReadTimeoutHandler( options.timeout, TimeUnit.MILLISECONDS ) );
|
||||
}
|
||||
|
||||
p.addLast(
|
||||
@@ -195,13 +203,13 @@ public class HttpRequest extends Resource<HttpRequest>
|
||||
catch( Exception e )
|
||||
{
|
||||
failure( "Could not connect" );
|
||||
if( ComputerCraft.logPeripheralErrors ) ComputerCraft.log.error( "Error in HTTP request", e );
|
||||
if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error in HTTP request", e );
|
||||
}
|
||||
}
|
||||
|
||||
void failure( String message )
|
||||
{
|
||||
if( tryClose() ) environment.queueEvent( FAILURE_EVENT, new Object[] { address, message } );
|
||||
if( tryClose() ) environment.queueEvent( FAILURE_EVENT, address, message );
|
||||
}
|
||||
|
||||
void failure( Throwable cause )
|
||||
@@ -227,14 +235,14 @@ public class HttpRequest extends Resource<HttpRequest>
|
||||
failure( message );
|
||||
}
|
||||
|
||||
void failure( String message, ILuaObject object )
|
||||
void failure( String message, HttpResponseHandle object )
|
||||
{
|
||||
if( tryClose() ) environment.queueEvent( FAILURE_EVENT, new Object[] { address, message, object } );
|
||||
if( tryClose() ) environment.queueEvent( FAILURE_EVENT, address, message, object );
|
||||
}
|
||||
|
||||
void success( ILuaObject object )
|
||||
void success( HttpResponseHandle object )
|
||||
{
|
||||
if( tryClose() ) environment.queueEvent( SUCCESS_EVENT, new Object[] { address, object } );
|
||||
if( tryClose() ) environment.queueEvent( SUCCESS_EVENT, address, object );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
package dan200.computercraft.core.apis.http.request;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.ILuaObject;
|
||||
import dan200.computercraft.core.apis.handles.ArrayByteChannel;
|
||||
import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
|
||||
import dan200.computercraft.core.apis.handles.EncodedReadableHandle;
|
||||
import dan200.computercraft.core.apis.handles.HandleGeneric;
|
||||
import dan200.computercraft.core.apis.http.HTTPRequestException;
|
||||
import dan200.computercraft.core.apis.http.NetworkUtils;
|
||||
import dan200.computercraft.core.apis.http.options.Options;
|
||||
import dan200.computercraft.core.tracking.TrackingField;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.CompositeByteBuf;
|
||||
@@ -45,18 +46,20 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
|
||||
|
||||
private final URI uri;
|
||||
private final HttpMethod method;
|
||||
private final Options options;
|
||||
|
||||
private Charset responseCharset;
|
||||
private final HttpHeaders responseHeaders = new DefaultHttpHeaders();
|
||||
private HttpResponseStatus responseStatus;
|
||||
private CompositeByteBuf responseBody;
|
||||
|
||||
HttpRequestHandler( HttpRequest request, URI uri, HttpMethod method )
|
||||
HttpRequestHandler( HttpRequest request, URI uri, HttpMethod method, Options options )
|
||||
{
|
||||
this.request = request;
|
||||
|
||||
this.uri = uri;
|
||||
this.method = method;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -153,7 +156,7 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
|
||||
if( partial.isReadable() )
|
||||
{
|
||||
// If we've read more than we're allowed to handle, abort as soon as possible.
|
||||
if( ComputerCraft.httpMaxDownload != 0 && responseBody.readableBytes() + partial.readableBytes() > ComputerCraft.httpMaxDownload )
|
||||
if( options.maxDownload != 0 && responseBody.readableBytes() + partial.readableBytes() > options.maxDownload )
|
||||
{
|
||||
closed = true;
|
||||
ctx.close();
|
||||
@@ -185,7 +188,7 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
|
||||
@Override
|
||||
public void exceptionCaught( ChannelHandlerContext ctx, Throwable cause )
|
||||
{
|
||||
if( ComputerCraft.logPeripheralErrors ) ComputerCraft.log.error( "Error handling HTTP response", cause );
|
||||
if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error handling HTTP response", cause );
|
||||
request.failure( cause );
|
||||
}
|
||||
|
||||
@@ -209,10 +212,10 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
|
||||
|
||||
// Prepare to queue an event
|
||||
ArrayByteChannel contents = new ArrayByteChannel( bytes );
|
||||
final ILuaObject reader = request.isBinary()
|
||||
? new BinaryReadableHandle( contents )
|
||||
HandleGeneric reader = request.isBinary()
|
||||
? BinaryReadableHandle.of( contents )
|
||||
: new EncodedReadableHandle( EncodedReadableHandle.open( contents, responseCharset ) );
|
||||
ILuaObject stream = new HttpResponseHandle( reader, status.code(), status.reasonPhrase(), headers );
|
||||
HttpResponseHandle stream = new HttpResponseHandle( reader, status.code(), status.reasonPhrase(), headers );
|
||||
|
||||
if( status.code() >= 200 && status.code() < 400 )
|
||||
{
|
||||
|
||||
@@ -5,62 +5,48 @@
|
||||
*/
|
||||
package dan200.computercraft.core.apis.http.request;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.ILuaObject;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.core.apis.handles.HandleGeneric;
|
||||
import dan200.computercraft.core.asm.ObjectSource;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Wraps a {@link dan200.computercraft.core.apis.handles.HandleGeneric} and provides additional methods for
|
||||
* getting the response code and headers.
|
||||
*/
|
||||
public class HttpResponseHandle implements ILuaObject
|
||||
public class HttpResponseHandle implements ObjectSource
|
||||
{
|
||||
private final String[] newMethods;
|
||||
private final int methodOffset;
|
||||
private final ILuaObject reader;
|
||||
private final Object reader;
|
||||
private final int responseCode;
|
||||
private final String responseStatus;
|
||||
private final Map<String, String> responseHeaders;
|
||||
|
||||
public HttpResponseHandle( @Nonnull ILuaObject reader, int responseCode, String responseStatus, @Nonnull Map<String, String> responseHeaders )
|
||||
public HttpResponseHandle( @Nonnull HandleGeneric reader, int responseCode, String responseStatus, @Nonnull Map<String, String> responseHeaders )
|
||||
{
|
||||
this.reader = reader;
|
||||
this.responseCode = responseCode;
|
||||
this.responseStatus = responseStatus;
|
||||
this.responseHeaders = responseHeaders;
|
||||
|
||||
String[] oldMethods = reader.getMethodNames();
|
||||
final int methodOffset = this.methodOffset = oldMethods.length;
|
||||
|
||||
final String[] newMethods = this.newMethods = Arrays.copyOf( oldMethods, oldMethods.length + 2 );
|
||||
newMethods[methodOffset + 0] = "getResponseCode";
|
||||
newMethods[methodOffset + 1] = "getResponseHeaders";
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
@LuaFunction
|
||||
public final Object[] getResponseCode()
|
||||
{
|
||||
return newMethods;
|
||||
return new Object[] { responseCode, responseStatus };
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final Map<String, String> getResponseHeaders()
|
||||
{
|
||||
return responseHeaders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException, InterruptedException
|
||||
public Iterable<Object> getExtra()
|
||||
{
|
||||
if( method < methodOffset ) return reader.callMethod( context, method, args );
|
||||
|
||||
switch( method - methodOffset )
|
||||
{
|
||||
case 0: // getResponseCode
|
||||
return new Object[] { responseCode, responseStatus };
|
||||
case 1: // getResponseHeaders
|
||||
return new Object[] { responseHeaders };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
return Collections.singletonList( reader );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import dan200.computercraft.core.apis.http.HTTPRequestException;
|
||||
import dan200.computercraft.core.apis.http.NetworkUtils;
|
||||
import dan200.computercraft.core.apis.http.Resource;
|
||||
import dan200.computercraft.core.apis.http.ResourceGroup;
|
||||
import dan200.computercraft.core.apis.http.options.Options;
|
||||
import dan200.computercraft.shared.util.IoUtil;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
@@ -130,6 +131,7 @@ public class Websocket extends Resource<Websocket>
|
||||
boolean ssl = uri.getScheme().equalsIgnoreCase( "wss" );
|
||||
|
||||
InetSocketAddress socketAddress = NetworkUtils.getAddress( uri.getHost(), uri.getPort(), ssl );
|
||||
Options options = NetworkUtils.getOptions( uri.getHost(), socketAddress );
|
||||
SslContext sslContext = ssl ? NetworkUtils.getSslContext() : null;
|
||||
|
||||
// getAddress may have a slight delay, so let's perform another cancellation check.
|
||||
@@ -151,14 +153,14 @@ public class Websocket extends Resource<Websocket>
|
||||
|
||||
WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker(
|
||||
uri, WebSocketVersion.V13, null, true, headers,
|
||||
ComputerCraft.httpMaxWebsocketMessage == 0 ? MAX_MESSAGE_SIZE : ComputerCraft.httpMaxWebsocketMessage
|
||||
options.websocketMessage <= 0 ? MAX_MESSAGE_SIZE : options.websocketMessage
|
||||
);
|
||||
|
||||
p.addLast(
|
||||
new HttpClientCodec(),
|
||||
new HttpObjectAggregator( 8192 ),
|
||||
WebSocketClientCompressionHandler.INSTANCE,
|
||||
new WebsocketHandler( Websocket.this, handshaker )
|
||||
new WebsocketHandler( Websocket.this, handshaker, options )
|
||||
);
|
||||
}
|
||||
} )
|
||||
@@ -178,16 +180,16 @@ public class Websocket extends Resource<Websocket>
|
||||
catch( Exception e )
|
||||
{
|
||||
failure( "Could not connect" );
|
||||
if( ComputerCraft.logPeripheralErrors ) ComputerCraft.log.error( "Error in websocket", e );
|
||||
if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error in websocket", e );
|
||||
}
|
||||
}
|
||||
|
||||
void success( Channel channel )
|
||||
void success( Channel channel, Options options )
|
||||
{
|
||||
if( isClosed() ) return;
|
||||
|
||||
WebsocketHandle handle = new WebsocketHandle( this, channel );
|
||||
environment().queueEvent( SUCCESS_EVENT, new Object[] { address, handle } );
|
||||
WebsocketHandle handle = new WebsocketHandle( this, options, channel );
|
||||
environment().queueEvent( SUCCESS_EVENT, address, handle );
|
||||
websocketHandle = createOwnerReference( handle );
|
||||
|
||||
checkClosed();
|
||||
@@ -195,18 +197,16 @@ public class Websocket extends Resource<Websocket>
|
||||
|
||||
void failure( String message )
|
||||
{
|
||||
if( tryClose() ) environment.queueEvent( FAILURE_EVENT, new Object[] { address, message } );
|
||||
if( tryClose() ) environment.queueEvent( FAILURE_EVENT, address, message );
|
||||
}
|
||||
|
||||
void close( int status, String reason )
|
||||
{
|
||||
if( tryClose() )
|
||||
{
|
||||
environment.queueEvent( CLOSE_EVENT, new Object[] {
|
||||
address,
|
||||
environment.queueEvent( CLOSE_EVENT, address,
|
||||
Strings.isNullOrEmpty( reason ) ? null : reason,
|
||||
status < 0 ? null : status,
|
||||
} );
|
||||
status < 0 ? null : status );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,11 +6,8 @@
|
||||
package dan200.computercraft.core.apis.http.websocket;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.ArgumentHelper;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.ILuaObject;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.*;
|
||||
import dan200.computercraft.core.apis.http.options.Options;
|
||||
import dan200.computercraft.core.tracking.TrackingField;
|
||||
import dan200.computercraft.shared.util.StringUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
@@ -19,109 +16,69 @@ import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.Closeable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
import static dan200.computercraft.api.lua.ArgumentHelper.optBoolean;
|
||||
import static dan200.computercraft.api.lua.LuaValues.checkFinite;
|
||||
import static dan200.computercraft.core.apis.IAPIEnvironment.TIMER_EVENT;
|
||||
import static dan200.computercraft.core.apis.http.websocket.Websocket.CLOSE_EVENT;
|
||||
import static dan200.computercraft.core.apis.http.websocket.Websocket.MESSAGE_EVENT;
|
||||
|
||||
public class WebsocketHandle implements ILuaObject, Closeable
|
||||
public class WebsocketHandle implements Closeable
|
||||
{
|
||||
private final Websocket websocket;
|
||||
private final Options options;
|
||||
private boolean closed = false;
|
||||
|
||||
private Channel channel;
|
||||
|
||||
public WebsocketHandle( Websocket websocket, Channel channel )
|
||||
public WebsocketHandle( Websocket websocket, Options options, Channel channel )
|
||||
{
|
||||
this.websocket = websocket;
|
||||
this.options = options;
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
@LuaFunction
|
||||
public final MethodResult result( Optional<Double> timeout ) throws LuaException
|
||||
{
|
||||
return new String[] { "receive", "send", "close" };
|
||||
checkOpen();
|
||||
int timeoutId = timeout.isPresent()
|
||||
? websocket.environment().startTimer( Math.round( checkFinite( 0, timeout.get() ) / 0.05 ) )
|
||||
: -1;
|
||||
|
||||
return new ReceiveCallback( timeoutId ).pull;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
|
||||
@LuaFunction
|
||||
public final void send( IArguments args ) throws LuaException
|
||||
{
|
||||
switch( method )
|
||||
checkOpen();
|
||||
|
||||
String text = StringUtil.toString( args.get( 0 ) );
|
||||
if( options.websocketMessage != 0 && text.length() > options.websocketMessage )
|
||||
{
|
||||
case 0: // receive
|
||||
{
|
||||
checkOpen();
|
||||
int timeoutId;
|
||||
if( arguments.length <= 0 || arguments[0] == null )
|
||||
{
|
||||
// We do this rather odd argument validation to ensure we can tell the difference between a
|
||||
// negative timeout and an absent one.
|
||||
timeoutId = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
double timeout = ArgumentHelper.getFiniteDouble( arguments, 0 );
|
||||
timeoutId = websocket.environment().startTimer( Math.round( timeout / 0.05 ) );
|
||||
}
|
||||
|
||||
while( true )
|
||||
{
|
||||
Object[] event = context.pullEvent( null );
|
||||
if( event.length >= 3 && Objects.equal( event[0], MESSAGE_EVENT ) && Objects.equal( event[1], websocket.address() ) )
|
||||
{
|
||||
return Arrays.copyOfRange( event, 2, event.length );
|
||||
}
|
||||
else if( event.length >= 2 && Objects.equal( event[0], CLOSE_EVENT ) && Objects.equal( event[1], websocket.address() ) && closed )
|
||||
{
|
||||
// If the socket is closed abort.
|
||||
return null;
|
||||
}
|
||||
else if( event.length >= 2 && timeoutId != -1 && Objects.equal( event[0], TIMER_EVENT )
|
||||
&& event[1] instanceof Number && ((Number) event[1]).intValue() == timeoutId )
|
||||
{
|
||||
// If we received a matching timer event then abort.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case 1: // send
|
||||
{
|
||||
checkOpen();
|
||||
|
||||
String text = arguments.length > 0 && arguments[0] != null ? arguments[0].toString() : "";
|
||||
if( ComputerCraft.httpMaxWebsocketMessage != 0 && text.length() > ComputerCraft.httpMaxWebsocketMessage )
|
||||
{
|
||||
throw new LuaException( "Message is too large" );
|
||||
}
|
||||
|
||||
boolean binary = optBoolean( arguments, 1, false );
|
||||
websocket.environment().addTrackingChange( TrackingField.WEBSOCKET_OUTGOING, text.length() );
|
||||
|
||||
Channel channel = this.channel;
|
||||
if( channel != null )
|
||||
{
|
||||
channel.writeAndFlush( binary
|
||||
? new BinaryWebSocketFrame( Unpooled.wrappedBuffer( StringUtil.encodeString( text ) ) )
|
||||
: new TextWebSocketFrame( text ) );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
case 2: // close
|
||||
close();
|
||||
websocket.close();
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
throw new LuaException( "Message is too large" );
|
||||
}
|
||||
|
||||
boolean binary = args.optBoolean( 1, false );
|
||||
websocket.environment().addTrackingChange( TrackingField.WEBSOCKET_OUTGOING, text.length() );
|
||||
|
||||
Channel channel = this.channel;
|
||||
if( channel != null )
|
||||
{
|
||||
channel.writeAndFlush( binary
|
||||
? new BinaryWebSocketFrame( Unpooled.wrappedBuffer( LuaValues.encode( text ) ) )
|
||||
: new TextWebSocketFrame( text ) );
|
||||
}
|
||||
}
|
||||
|
||||
@LuaFunction( "close" )
|
||||
public final void doClose()
|
||||
{
|
||||
close();
|
||||
websocket.close();
|
||||
}
|
||||
|
||||
private void checkOpen() throws LuaException
|
||||
@@ -141,4 +98,38 @@ public class WebsocketHandle implements ILuaObject, Closeable
|
||||
this.channel = null;
|
||||
}
|
||||
}
|
||||
|
||||
private final class ReceiveCallback implements ILuaCallback
|
||||
{
|
||||
final MethodResult pull = MethodResult.pullEvent( null, this );
|
||||
private final int timeoutId;
|
||||
|
||||
ReceiveCallback( int timeoutId )
|
||||
{
|
||||
this.timeoutId = timeoutId;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MethodResult resume( Object[] event )
|
||||
{
|
||||
if( event.length >= 3 && Objects.equal( event[0], MESSAGE_EVENT ) && Objects.equal( event[1], websocket.address() ) )
|
||||
{
|
||||
return MethodResult.of( Arrays.copyOfRange( event, 2, event.length ) );
|
||||
}
|
||||
else if( event.length >= 2 && Objects.equal( event[0], CLOSE_EVENT ) && Objects.equal( event[1], websocket.address() ) && closed )
|
||||
{
|
||||
// If the socket is closed abort.
|
||||
return MethodResult.of();
|
||||
}
|
||||
else if( event.length >= 2 && timeoutId != -1 && Objects.equal( event[0], TIMER_EVENT )
|
||||
&& event[1] instanceof Number && ((Number) event[1]).intValue() == timeoutId )
|
||||
{
|
||||
// If we received a matching timer event then abort.
|
||||
return MethodResult.of();
|
||||
}
|
||||
|
||||
return pull;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ package dan200.computercraft.core.apis.http.websocket;
|
||||
|
||||
import dan200.computercraft.core.apis.http.HTTPRequestException;
|
||||
import dan200.computercraft.core.apis.http.NetworkUtils;
|
||||
import dan200.computercraft.core.apis.http.options.Options;
|
||||
import dan200.computercraft.core.tracking.TrackingField;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ConnectTimeoutException;
|
||||
@@ -23,11 +24,13 @@ public class WebsocketHandler extends SimpleChannelInboundHandler<Object>
|
||||
{
|
||||
private final Websocket websocket;
|
||||
private final WebSocketClientHandshaker handshaker;
|
||||
private final Options options;
|
||||
|
||||
public WebsocketHandler( Websocket websocket, WebSocketClientHandshaker handshaker )
|
||||
public WebsocketHandler( Websocket websocket, WebSocketClientHandshaker handshaker, Options options )
|
||||
{
|
||||
this.handshaker = handshaker;
|
||||
this.websocket = websocket;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,7 +55,7 @@ public class WebsocketHandler extends SimpleChannelInboundHandler<Object>
|
||||
if( !handshaker.isHandshakeComplete() )
|
||||
{
|
||||
handshaker.finishHandshake( ctx.channel(), (FullHttpResponse) msg );
|
||||
websocket.success( ctx.channel() );
|
||||
websocket.success( ctx.channel(), options );
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -68,14 +71,14 @@ public class WebsocketHandler extends SimpleChannelInboundHandler<Object>
|
||||
String data = ((TextWebSocketFrame) frame).text();
|
||||
|
||||
websocket.environment().addTrackingChange( TrackingField.WEBSOCKET_INCOMING, data.length() );
|
||||
websocket.environment().queueEvent( MESSAGE_EVENT, new Object[] { websocket.address(), data, false } );
|
||||
websocket.environment().queueEvent( MESSAGE_EVENT, websocket.address(), data, false );
|
||||
}
|
||||
else if( frame instanceof BinaryWebSocketFrame )
|
||||
{
|
||||
byte[] converted = NetworkUtils.toBytes( frame.content() );
|
||||
|
||||
websocket.environment().addTrackingChange( TrackingField.WEBSOCKET_INCOMING, converted.length );
|
||||
websocket.environment().queueEvent( MESSAGE_EVENT, new Object[] { websocket.address(), converted, true } );
|
||||
websocket.environment().queueEvent( MESSAGE_EVENT, websocket.address(), converted, true );
|
||||
}
|
||||
else if( frame instanceof CloseWebSocketFrame )
|
||||
{
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.asm;
|
||||
|
||||
import java.security.ProtectionDomain;
|
||||
|
||||
final class DeclaringClassLoader extends ClassLoader
|
||||
{
|
||||
static final DeclaringClassLoader INSTANCE = new DeclaringClassLoader();
|
||||
|
||||
private DeclaringClassLoader()
|
||||
{
|
||||
super( DeclaringClassLoader.class.getClassLoader() );
|
||||
}
|
||||
|
||||
Class<?> define( String name, byte[] bytes, ProtectionDomain protectionDomain ) throws ClassFormatError
|
||||
{
|
||||
return defineClass( name, bytes, 0, bytes.length, protectionDomain );
|
||||
}
|
||||
}
|
||||
320
src/main/java/dan200/computercraft/core/asm/Generator.java
Normal file
320
src/main/java/dan200/computercraft/core/asm/Generator.java
Normal file
@@ -0,0 +1,320 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.asm;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.primitives.Primitives;
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.IArguments;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.objectweb.asm.Opcodes.*;
|
||||
|
||||
public class Generator<T>
|
||||
{
|
||||
private static final AtomicInteger METHOD_ID = new AtomicInteger();
|
||||
|
||||
private static final String METHOD_NAME = "apply";
|
||||
private static final String[] EXCEPTIONS = new String[] { Type.getInternalName( LuaException.class ) };
|
||||
|
||||
private static final String INTERNAL_METHOD_RESULT = Type.getInternalName( MethodResult.class );
|
||||
private static final String DESC_METHOD_RESULT = Type.getDescriptor( MethodResult.class );
|
||||
|
||||
private static final String INTERNAL_ARGUMENTS = Type.getInternalName( IArguments.class );
|
||||
private static final String DESC_ARGUMENTS = Type.getDescriptor( IArguments.class );
|
||||
|
||||
private final Class<T> base;
|
||||
private final List<Class<?>> context;
|
||||
|
||||
private final String[] interfaces;
|
||||
private final String methodDesc;
|
||||
|
||||
private final Function<T, T> wrap;
|
||||
|
||||
private final LoadingCache<Class<?>, List<NamedMethod<T>>> classCache = CacheBuilder
|
||||
.newBuilder()
|
||||
.build( CacheLoader.from( this::build ) );
|
||||
|
||||
private final LoadingCache<Method, Optional<T>> methodCache = CacheBuilder
|
||||
.newBuilder()
|
||||
.build( CacheLoader.from( this::build ) );
|
||||
|
||||
Generator( Class<T> base, List<Class<?>> context, Function<T, T> wrap )
|
||||
{
|
||||
this.base = base;
|
||||
this.context = context;
|
||||
this.interfaces = new String[] { Type.getInternalName( base ) };
|
||||
this.wrap = wrap;
|
||||
|
||||
StringBuilder methodDesc = new StringBuilder().append( "(Ljava/lang/Object;" );
|
||||
for( Class<?> klass : context ) methodDesc.append( Type.getDescriptor( klass ) );
|
||||
methodDesc.append( DESC_ARGUMENTS ).append( ")" ).append( DESC_METHOD_RESULT );
|
||||
this.methodDesc = methodDesc.toString();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<NamedMethod<T>> getMethods( @Nonnull Class<?> klass )
|
||||
{
|
||||
try
|
||||
{
|
||||
return classCache.get( klass );
|
||||
}
|
||||
catch( ExecutionException e )
|
||||
{
|
||||
ComputerCraft.log.error( "Error getting methods for {}.", klass.getName(), e.getCause() );
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private List<NamedMethod<T>> build( Class<?> klass )
|
||||
{
|
||||
ArrayList<NamedMethod<T>> methods = null;
|
||||
for( Method method : klass.getMethods() )
|
||||
{
|
||||
LuaFunction annotation = method.getAnnotation( LuaFunction.class );
|
||||
if( annotation == null ) continue;
|
||||
|
||||
T instance = methodCache.getUnchecked( method ).orElse( null );
|
||||
if( instance == null ) continue;
|
||||
|
||||
if( methods == null ) methods = new ArrayList<>();
|
||||
|
||||
if( annotation.mainThread() ) instance = wrap.apply( instance );
|
||||
|
||||
String[] names = annotation.value();
|
||||
boolean isSimple = method.getReturnType() != MethodResult.class && !annotation.mainThread();
|
||||
if( names.length == 0 )
|
||||
{
|
||||
methods.add( new NamedMethod<>( method.getName(), instance, isSimple ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
for( String name : names )
|
||||
{
|
||||
methods.add( new NamedMethod<>( name, instance, isSimple ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( methods == null ) return Collections.emptyList();
|
||||
methods.trimToSize();
|
||||
return Collections.unmodifiableList( methods );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private Optional<T> build( Method method )
|
||||
{
|
||||
String name = method.getDeclaringClass().getName() + "." + method.getName();
|
||||
int modifiers = method.getModifiers();
|
||||
if( !Modifier.isFinal( modifiers ) )
|
||||
{
|
||||
ComputerCraft.log.warn( "Lua Method {} should be final.", name );
|
||||
}
|
||||
|
||||
if( Modifier.isStatic( modifiers ) || !Modifier.isPublic( modifiers ) )
|
||||
{
|
||||
ComputerCraft.log.error( "Lua Method {} should be a public instance method.", name );
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if( !Modifier.isPublic( method.getDeclaringClass().getModifiers() ) )
|
||||
{
|
||||
ComputerCraft.log.error( "Lua Method {} should be on a public class.", name );
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
ComputerCraft.log.debug( "Generating method wrapper for {}.", name );
|
||||
|
||||
Class<?>[] exceptions = method.getExceptionTypes();
|
||||
for( Class<?> exception : exceptions )
|
||||
{
|
||||
if( exception != LuaException.class )
|
||||
{
|
||||
ComputerCraft.log.error( "Lua Method {} cannot throw {}.", name, exception.getName() );
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
String className = method.getDeclaringClass().getName() + "$cc$" + method.getName() + METHOD_ID.getAndIncrement();
|
||||
byte[] bytes = generate( className, method );
|
||||
if( bytes == null ) return Optional.empty();
|
||||
|
||||
Class<?> klass = DeclaringClassLoader.INSTANCE.define( className, bytes, method.getDeclaringClass().getProtectionDomain() );
|
||||
return Optional.of( klass.asSubclass( base ).newInstance() );
|
||||
}
|
||||
catch( InstantiationException | IllegalAccessException | ClassFormatError | RuntimeException e )
|
||||
{
|
||||
ComputerCraft.log.error( "Error generating wrapper for {}.", name, e );
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private byte[] generate( String className, Method method )
|
||||
{
|
||||
String internalName = className.replace( ".", "/" );
|
||||
|
||||
// Construct a public final class which extends Object and implements MethodInstance.Delegate
|
||||
ClassWriter cw = new ClassWriter( ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS );
|
||||
cw.visit( V1_8, ACC_PUBLIC | ACC_FINAL, internalName, null, "java/lang/Object", interfaces );
|
||||
cw.visitSource( "CC generated method", null );
|
||||
|
||||
{ // Constructor just invokes super.
|
||||
MethodVisitor mw = cw.visitMethod( ACC_PUBLIC, "<init>", "()V", null, null );
|
||||
mw.visitCode();
|
||||
mw.visitVarInsn( ALOAD, 0 );
|
||||
mw.visitMethodInsn( INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false );
|
||||
mw.visitInsn( RETURN );
|
||||
mw.visitMaxs( 0, 0 );
|
||||
mw.visitEnd();
|
||||
}
|
||||
|
||||
{
|
||||
MethodVisitor mw = cw.visitMethod( ACC_PUBLIC, METHOD_NAME, methodDesc, null, EXCEPTIONS );
|
||||
mw.visitCode();
|
||||
mw.visitVarInsn( ALOAD, 1 );
|
||||
mw.visitTypeInsn( CHECKCAST, Type.getInternalName( method.getDeclaringClass() ) );
|
||||
|
||||
int argIndex = 0;
|
||||
for( java.lang.reflect.Type genericArg : method.getGenericParameterTypes() )
|
||||
{
|
||||
Boolean loadedArg = loadArg( mw, method, genericArg, argIndex );
|
||||
if( loadedArg == null ) return null;
|
||||
if( loadedArg ) argIndex++;
|
||||
}
|
||||
|
||||
mw.visitMethodInsn( INVOKEVIRTUAL, Type.getInternalName( method.getDeclaringClass() ), method.getName(),
|
||||
Type.getMethodDescriptor( method ), false );
|
||||
|
||||
// We allow a reasonable amount of flexibility on the return value's type. Alongside the obvious MethodResult,
|
||||
// we convert basic types into an immediate result.
|
||||
Class<?> ret = method.getReturnType();
|
||||
if( ret != MethodResult.class )
|
||||
{
|
||||
if( ret == void.class )
|
||||
{
|
||||
mw.visitMethodInsn( INVOKESTATIC, INTERNAL_METHOD_RESULT, "of", "()" + DESC_METHOD_RESULT, false );
|
||||
}
|
||||
else if( ret.isPrimitive() )
|
||||
{
|
||||
Class<?> boxed = Primitives.wrap( ret );
|
||||
mw.visitMethodInsn( INVOKESTATIC, Type.getInternalName( boxed ), "valueOf", "(" + Type.getDescriptor( ret ) + ")" + Type.getDescriptor( boxed ), false );
|
||||
mw.visitMethodInsn( INVOKESTATIC, INTERNAL_METHOD_RESULT, "of", "(Ljava/lang/Object;)" + DESC_METHOD_RESULT, false );
|
||||
}
|
||||
else if( ret == Object[].class )
|
||||
{
|
||||
mw.visitMethodInsn( INVOKESTATIC, INTERNAL_METHOD_RESULT, "of", "([Ljava/lang/Object;)" + DESC_METHOD_RESULT, false );
|
||||
}
|
||||
else
|
||||
{
|
||||
mw.visitMethodInsn( INVOKESTATIC, INTERNAL_METHOD_RESULT, "of", "(Ljava/lang/Object;)" + DESC_METHOD_RESULT, false );
|
||||
}
|
||||
}
|
||||
|
||||
mw.visitInsn( ARETURN );
|
||||
|
||||
mw.visitMaxs( 0, 0 );
|
||||
mw.visitEnd();
|
||||
}
|
||||
|
||||
cw.visitEnd();
|
||||
|
||||
return cw.toByteArray();
|
||||
}
|
||||
|
||||
private Boolean loadArg( MethodVisitor mw, Method method, java.lang.reflect.Type genericArg, int argIndex )
|
||||
{
|
||||
Class<?> arg = Reflect.getRawType( method, genericArg, true );
|
||||
if( arg == null ) return null;
|
||||
|
||||
if( arg == IArguments.class )
|
||||
{
|
||||
mw.visitVarInsn( ALOAD, 2 + context.size() );
|
||||
return false;
|
||||
}
|
||||
|
||||
int idx = context.indexOf( arg );
|
||||
if( idx >= 0 )
|
||||
{
|
||||
mw.visitVarInsn( ALOAD, 2 + idx );
|
||||
return false;
|
||||
}
|
||||
|
||||
if( arg == Optional.class )
|
||||
{
|
||||
Class<?> klass = Reflect.getRawType( method, TypeToken.of( genericArg ).resolveType( Reflect.OPTIONAL_IN ).getType(), false );
|
||||
if( klass == null ) return null;
|
||||
|
||||
if( Enum.class.isAssignableFrom( klass ) && klass != Enum.class )
|
||||
{
|
||||
mw.visitVarInsn( ALOAD, 2 + context.size() );
|
||||
Reflect.loadInt( mw, argIndex );
|
||||
mw.visitLdcInsn( Type.getType( klass ) );
|
||||
mw.visitMethodInsn( INVOKEINTERFACE, INTERNAL_ARGUMENTS, "optEnum", "(ILjava/lang/Class;)Ljava/util/Optional;", true );
|
||||
return true;
|
||||
}
|
||||
|
||||
String name = Reflect.getLuaName( Primitives.unwrap( klass ) );
|
||||
if( name != null )
|
||||
{
|
||||
mw.visitVarInsn( ALOAD, 2 + context.size() );
|
||||
Reflect.loadInt( mw, argIndex );
|
||||
mw.visitMethodInsn( INVOKEINTERFACE, INTERNAL_ARGUMENTS, "opt" + name, "(I)Ljava/util/Optional;", true );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if( Enum.class.isAssignableFrom( arg ) && arg != Enum.class )
|
||||
{
|
||||
mw.visitVarInsn( ALOAD, 2 + context.size() );
|
||||
Reflect.loadInt( mw, argIndex );
|
||||
mw.visitLdcInsn( Type.getType( arg ) );
|
||||
mw.visitMethodInsn( INVOKEINTERFACE, INTERNAL_ARGUMENTS, "getEnum", "(ILjava/lang/Class;)Ljava/lang/Enum;", true );
|
||||
mw.visitTypeInsn( CHECKCAST, Type.getInternalName( arg ) );
|
||||
return true;
|
||||
}
|
||||
|
||||
String name = arg == Object.class ? "" : Reflect.getLuaName( arg );
|
||||
if( name != null )
|
||||
{
|
||||
if( Reflect.getRawType( method, genericArg, false ) == null ) return null;
|
||||
|
||||
mw.visitVarInsn( ALOAD, 2 + context.size() );
|
||||
Reflect.loadInt( mw, argIndex );
|
||||
mw.visitMethodInsn( INVOKEINTERFACE, INTERNAL_ARGUMENTS, "get" + name, "(I)" + Type.getDescriptor( arg ), true );
|
||||
return true;
|
||||
}
|
||||
|
||||
ComputerCraft.log.error( "Unknown parameter type {} for method {}.{}.",
|
||||
arg.getName(), method.getDeclaringClass().getName(), method.getName() );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
41
src/main/java/dan200/computercraft/core/asm/IntCache.java
Normal file
41
src/main/java/dan200/computercraft/core/asm/IntCache.java
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.asm;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
public final class IntCache<T>
|
||||
{
|
||||
private final IntFunction<T> factory;
|
||||
private volatile Object[] cache = new Object[16];
|
||||
|
||||
IntCache( IntFunction<T> factory )
|
||||
{
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
@SuppressWarnings( "unchecked" )
|
||||
public T get( int index )
|
||||
{
|
||||
if( index < 0 ) throw new IllegalArgumentException( "index < 0" );
|
||||
|
||||
if( index <= cache.length )
|
||||
{
|
||||
T current = (T) cache[index];
|
||||
if( current != null ) return current;
|
||||
}
|
||||
|
||||
synchronized( this )
|
||||
{
|
||||
if( index > cache.length ) cache = Arrays.copyOf( cache, Math.max( cache.length * 2, index ) );
|
||||
T current = (T) cache[index];
|
||||
if( current == null ) cache[index] = current = factory.apply( index );
|
||||
return current;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/main/java/dan200/computercraft/core/asm/LuaMethod.java
Normal file
30
src/main/java/dan200/computercraft/core/asm/LuaMethod.java
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.asm;
|
||||
|
||||
import dan200.computercraft.api.lua.*;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Collections;
|
||||
|
||||
public interface LuaMethod
|
||||
{
|
||||
Generator<LuaMethod> GENERATOR = new Generator<>( LuaMethod.class, Collections.singletonList( ILuaContext.class ),
|
||||
m -> ( target, context, args ) -> {
|
||||
long id = context.issueMainThreadTask( () -> TaskCallback.checkUnwrap( m.apply( target, context, args ) ) );
|
||||
return new TaskCallback( id ).pull;
|
||||
} );
|
||||
|
||||
IntCache<LuaMethod> DYNAMIC = new IntCache<>(
|
||||
method -> ( instance, context, args ) -> ((IDynamicLuaObject) instance).callMethod( context, method, args )
|
||||
);
|
||||
|
||||
String[] EMPTY_METHODS = new String[0];
|
||||
|
||||
@Nonnull
|
||||
MethodResult apply( @Nonnull Object target, @Nonnull ILuaContext context, @Nonnull IArguments args ) throws LuaException;
|
||||
}
|
||||
40
src/main/java/dan200/computercraft/core/asm/NamedMethod.java
Normal file
40
src/main/java/dan200/computercraft/core/asm/NamedMethod.java
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.asm;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class NamedMethod<T>
|
||||
{
|
||||
private final String name;
|
||||
private final T method;
|
||||
private final boolean nonYielding;
|
||||
|
||||
NamedMethod( String name, T method, boolean nonYielding )
|
||||
{
|
||||
this.name = name;
|
||||
this.method = method;
|
||||
this.nonYielding = nonYielding;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public T getMethod()
|
||||
{
|
||||
return method;
|
||||
}
|
||||
|
||||
public boolean nonYielding()
|
||||
{
|
||||
return nonYielding;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.asm;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* A Lua object which exposes additional methods.
|
||||
*
|
||||
* This can be used to merge multiple objects together into one. Ideally this'd be part of the API, but I'm not entirely
|
||||
* happy with the interface - something I'd like to think about first.
|
||||
*/
|
||||
public interface ObjectSource
|
||||
{
|
||||
Iterable<Object> getExtra();
|
||||
|
||||
static <T> void allMethods( Generator<T> generator, Object object, BiConsumer<Object, NamedMethod<T>> accept )
|
||||
{
|
||||
for( NamedMethod<T> method : generator.getMethods( object.getClass() ) ) accept.accept( object, method );
|
||||
|
||||
if( object instanceof ObjectSource )
|
||||
{
|
||||
for( Object extra : ((ObjectSource) object).getExtra() )
|
||||
{
|
||||
for( NamedMethod<T> method : generator.getMethods( extra.getClass() ) ) accept.accept( extra, method );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.asm;
|
||||
|
||||
import dan200.computercraft.api.lua.IArguments;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IDynamicPeripheral;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Arrays;
|
||||
|
||||
public interface PeripheralMethod
|
||||
{
|
||||
Generator<PeripheralMethod> GENERATOR = new Generator<>( PeripheralMethod.class, Arrays.asList( ILuaContext.class, IComputerAccess.class ),
|
||||
m -> ( target, context, computer, args ) -> {
|
||||
long id = context.issueMainThreadTask( () -> TaskCallback.checkUnwrap( m.apply( target, context, computer, args ) ) );
|
||||
return new TaskCallback( id ).pull;
|
||||
} );
|
||||
|
||||
IntCache<PeripheralMethod> DYNAMIC = new IntCache<>(
|
||||
method -> ( instance, context, computer, args ) -> ((IDynamicPeripheral) instance).callMethod( computer, context, method, args )
|
||||
);
|
||||
|
||||
@Nonnull
|
||||
MethodResult apply( @Nonnull Object target, @Nonnull ILuaContext context, @Nonnull IComputerAccess computer, @Nonnull IArguments args ) throws LuaException;
|
||||
}
|
||||
95
src/main/java/dan200/computercraft/core/asm/Reflect.java
Normal file
95
src/main/java/dan200/computercraft/core/asm/Reflect.java
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.asm;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.lang.reflect.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.objectweb.asm.Opcodes.ICONST_0;
|
||||
|
||||
final class Reflect
|
||||
{
|
||||
static final java.lang.reflect.Type OPTIONAL_IN = Optional.class.getTypeParameters()[0];
|
||||
|
||||
private Reflect()
|
||||
{
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static String getLuaName( Class<?> klass )
|
||||
{
|
||||
if( klass.isPrimitive() )
|
||||
{
|
||||
if( klass == int.class ) return "Int";
|
||||
if( klass == boolean.class ) return "Boolean";
|
||||
if( klass == double.class ) return "Double";
|
||||
if( klass == long.class ) return "Long";
|
||||
}
|
||||
else
|
||||
{
|
||||
if( klass == Map.class ) return "Table";
|
||||
if( klass == String.class ) return "String";
|
||||
if( klass == ByteBuffer.class ) return "Bytes";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static Class<?> getRawType( Method method, Type root, boolean allowParameter )
|
||||
{
|
||||
Type underlying = root;
|
||||
while( true )
|
||||
{
|
||||
if( underlying instanceof Class<?> ) return (Class<?>) underlying;
|
||||
|
||||
if( underlying instanceof ParameterizedType )
|
||||
{
|
||||
ParameterizedType type = (ParameterizedType) underlying;
|
||||
if( !allowParameter )
|
||||
{
|
||||
for( java.lang.reflect.Type arg : type.getActualTypeArguments() )
|
||||
{
|
||||
if( arg instanceof WildcardType ) continue;
|
||||
if( arg instanceof TypeVariable && ((TypeVariable<?>) arg).getName().startsWith( "capture#" ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ComputerCraft.log.error( "Method {}.{} has generic type {} with non-wildcard argument {}.", method.getDeclaringClass(), method.getName(), root, arg );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Continue to extract from this child
|
||||
underlying = type.getRawType();
|
||||
continue;
|
||||
}
|
||||
|
||||
ComputerCraft.log.error( "Method {}.{} has unknown generic type {}.", method.getDeclaringClass(), method.getName(), root );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static void loadInt( MethodVisitor visitor, int value )
|
||||
{
|
||||
if( value >= -1 && value <= 5 )
|
||||
{
|
||||
visitor.visitInsn( ICONST_0 + value );
|
||||
}
|
||||
else
|
||||
{
|
||||
visitor.visitLdcInsn( value );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.asm;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaCallback;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Arrays;
|
||||
|
||||
class TaskCallback implements ILuaCallback
|
||||
{
|
||||
final MethodResult pull = MethodResult.pullEvent( "task_complete", this );
|
||||
private final long task;
|
||||
|
||||
TaskCallback( long task )
|
||||
{
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MethodResult resume( Object[] response ) throws LuaException
|
||||
{
|
||||
if( response.length < 3 || !(response[1] instanceof Number) || !(response[2] instanceof Boolean) )
|
||||
{
|
||||
return pull;
|
||||
}
|
||||
|
||||
if( ((Number) response[1]).longValue() != task ) return pull;
|
||||
|
||||
if( (Boolean) response[2] )
|
||||
{
|
||||
// Extract the return values from the event and return them
|
||||
return MethodResult.of( Arrays.copyOfRange( response, 3, response.length ) );
|
||||
}
|
||||
else if( response.length >= 4 && response[3] instanceof String )
|
||||
{
|
||||
// Extract the error message from the event and raise it
|
||||
throw new LuaException( (String) response[3] );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new LuaException( "error" );
|
||||
}
|
||||
}
|
||||
|
||||
public static Object[] checkUnwrap( MethodResult result )
|
||||
{
|
||||
if( result.getCallback() != null ) throw new IllegalStateException( "Cannot return MethodResult currently" );
|
||||
return result.getResult();
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,6 @@
|
||||
package dan200.computercraft.core.computer;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A wrapper for {@link ILuaAPI}s which cleans up after a {@link ComputerSystem} when the computer is shutdown.
|
||||
@@ -51,17 +46,8 @@ final class ApiWrapper implements ILuaAPI
|
||||
system.unmountAll();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
public ILuaAPI getDelegate()
|
||||
{
|
||||
return delegate.getMethodNames();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
|
||||
{
|
||||
return delegate.callMethod( context, method, arguments );
|
||||
return delegate;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ public class Computer
|
||||
// Additional state about the computer and its environment.
|
||||
private boolean m_blinking = false;
|
||||
private final Environment internalEnvironment = new Environment( this );
|
||||
private AtomicBoolean externalOutputChanged = new AtomicBoolean();
|
||||
private final AtomicBoolean externalOutputChanged = new AtomicBoolean();
|
||||
|
||||
private boolean startRequested;
|
||||
private int m_ticksSinceStart = -1;
|
||||
|
||||
@@ -388,8 +388,8 @@ final class ComputerExecutor
|
||||
// Create the lua machine
|
||||
ILuaMachine machine = new CobaltLuaMachine( computer, timeout );
|
||||
|
||||
// Add the APIs
|
||||
for( ILuaAPI api : apis ) machine.addAPI( api );
|
||||
// Add the APIs. We unwrap them (yes, this is horrible) to get access to the underlying object.
|
||||
for( ILuaAPI api : apis ) machine.addAPI( api instanceof ApiWrapper ? ((ApiWrapper) api).getDelegate() : api );
|
||||
|
||||
// Start the machine running the bios resource
|
||||
MachineResult result = machine.loadBios( biosStream );
|
||||
|
||||
@@ -136,7 +136,7 @@ public final class ComputerThread
|
||||
if( runners == null )
|
||||
{
|
||||
// TODO: Change the runners length on config reloads
|
||||
runners = new TaskRunner[ComputerCraft.computer_threads];
|
||||
runners = new TaskRunner[ComputerCraft.computerThreads];
|
||||
|
||||
// latency and minPeriod are scaled by 1 + floor(log2(threads)). We can afford to execute tasks for
|
||||
// longer when executing on more than one thread.
|
||||
@@ -518,7 +518,7 @@ public final class ComputerThread
|
||||
|
||||
private static void timeoutTask( ComputerExecutor executor, Thread thread, long time )
|
||||
{
|
||||
if( !ComputerCraft.logPeripheralErrors ) return;
|
||||
if( !ComputerCraft.logComputerErrors ) return;
|
||||
|
||||
StringBuilder builder = new StringBuilder()
|
||||
.append( "Terminating computer #" ).append( executor.getComputer().getID() )
|
||||
|
||||
@@ -111,7 +111,7 @@ public final class Environment implements IAPIEnvironment
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queueEvent( String event, Object[] args )
|
||||
public void queueEvent( String event, Object... args )
|
||||
{
|
||||
computer.queueEvent( event, args );
|
||||
}
|
||||
@@ -226,7 +226,7 @@ public final class Environment implements IAPIEnvironment
|
||||
if( inputChanged )
|
||||
{
|
||||
inputChanged = false;
|
||||
queueEvent( "redstone", null );
|
||||
queueEvent( "redstone" );
|
||||
}
|
||||
|
||||
synchronized( timers )
|
||||
@@ -241,7 +241,7 @@ public final class Environment implements IAPIEnvironment
|
||||
if( timer.ticksLeft <= 0 )
|
||||
{
|
||||
// Queue the "timer" event
|
||||
queueEvent( TIMER_EVENT, new Object[] { entry.getIntKey() } );
|
||||
queueEvent( TIMER_EVENT, entry.getIntKey() );
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.lua;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.IArguments;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
import dan200.computercraft.core.asm.LuaMethod;
|
||||
import org.squiddev.cobalt.LuaError;
|
||||
import org.squiddev.cobalt.LuaState;
|
||||
import org.squiddev.cobalt.Varargs;
|
||||
import org.squiddev.cobalt.function.VarArgFunction;
|
||||
|
||||
/**
|
||||
* An "optimised" version of {@link ResultInterpreterFunction} which is guaranteed to never yield.
|
||||
*
|
||||
* As we never yield, we do not need to push a function to the stack, which removes a small amount of overhead.
|
||||
*/
|
||||
class BasicFunction extends VarArgFunction
|
||||
{
|
||||
private final CobaltLuaMachine machine;
|
||||
private final LuaMethod method;
|
||||
private final Object instance;
|
||||
private final ILuaContext context;
|
||||
private final String name;
|
||||
|
||||
BasicFunction( CobaltLuaMachine machine, LuaMethod method, Object instance, ILuaContext context, String name )
|
||||
{
|
||||
this.machine = machine;
|
||||
this.method = method;
|
||||
this.instance = instance;
|
||||
this.context = context;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Varargs invoke( LuaState luaState, Varargs args ) throws LuaError
|
||||
{
|
||||
IArguments arguments = CobaltLuaMachine.toArguments( args );
|
||||
MethodResult results;
|
||||
try
|
||||
{
|
||||
results = method.apply( instance, context, arguments );
|
||||
}
|
||||
catch( LuaException e )
|
||||
{
|
||||
throw wrap( e );
|
||||
}
|
||||
catch( Throwable t )
|
||||
{
|
||||
if( ComputerCraft.logComputerErrors )
|
||||
{
|
||||
ComputerCraft.log.error( "Error calling " + name + " on " + instance, t );
|
||||
}
|
||||
throw new LuaError( "Java Exception Thrown: " + t, 0 );
|
||||
}
|
||||
|
||||
if( results.getCallback() != null )
|
||||
{
|
||||
throw new IllegalStateException( "Cannot have a yielding non-yielding function" );
|
||||
}
|
||||
return machine.toValues( results.getResult() );
|
||||
}
|
||||
|
||||
public static LuaError wrap( LuaException exception )
|
||||
{
|
||||
return exception.hasLevel() ? new LuaError( exception.getMessage() ) : new LuaError( exception.getMessage(), exception.getLevel() );
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ package dan200.computercraft.core.lua;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.*;
|
||||
import dan200.computercraft.core.asm.LuaMethod;
|
||||
import dan200.computercraft.core.asm.ObjectSource;
|
||||
import dan200.computercraft.core.computer.Computer;
|
||||
import dan200.computercraft.core.computer.MainThread;
|
||||
import dan200.computercraft.core.computer.TimeoutState;
|
||||
@@ -20,13 +22,13 @@ import org.squiddev.cobalt.debug.DebugFrame;
|
||||
import org.squiddev.cobalt.debug.DebugHandler;
|
||||
import org.squiddev.cobalt.debug.DebugState;
|
||||
import org.squiddev.cobalt.function.LuaFunction;
|
||||
import org.squiddev.cobalt.function.VarArgFunction;
|
||||
import org.squiddev.cobalt.lib.*;
|
||||
import org.squiddev.cobalt.lib.platform.VoidResourceManipulator;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
@@ -93,7 +95,7 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
m_globals.load( state, new CoroutineLib() );
|
||||
m_globals.load( state, new Bit32Lib() );
|
||||
m_globals.load( state, new Utf8Lib() );
|
||||
if( ComputerCraft.debug_enable ) m_globals.load( state, new DebugLib() );
|
||||
if( ComputerCraft.debugEnable ) m_globals.load( state, new DebugLib() );
|
||||
|
||||
// Remove globals we don't want to expose
|
||||
m_globals.rawset( "collectgarbage", Constants.NIL );
|
||||
@@ -104,8 +106,8 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
// Add version globals
|
||||
m_globals.rawset( "_VERSION", valueOf( "Lua 5.1" ) );
|
||||
m_globals.rawset( "_HOST", valueOf( computer.getAPIEnvironment().getComputerEnvironment().getHostString() ) );
|
||||
m_globals.rawset( "_CC_DEFAULT_SETTINGS", valueOf( ComputerCraft.default_computer_settings ) );
|
||||
if( ComputerCraft.disable_lua51_features )
|
||||
m_globals.rawset( "_CC_DEFAULT_SETTINGS", valueOf( ComputerCraft.defaultComputerSettings ) );
|
||||
if( ComputerCraft.disableLua51Features )
|
||||
{
|
||||
m_globals.rawset( "_CC_DISABLE_LUA51_FEATURES", Constants.TRUE );
|
||||
}
|
||||
@@ -116,11 +118,14 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
{
|
||||
// Add the methods of an API to the global table
|
||||
LuaTable table = wrapLuaObject( api );
|
||||
String[] names = api.getNames();
|
||||
for( String name : names )
|
||||
if( table == null )
|
||||
{
|
||||
m_globals.rawset( name, table );
|
||||
ComputerCraft.log.warn( "API {} does not provide any methods", api );
|
||||
table = new LuaTable();
|
||||
}
|
||||
|
||||
String[] names = api.getNames();
|
||||
for( String name : names ) m_globals.rawset( name, table );
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -216,54 +221,38 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
m_globals = null;
|
||||
}
|
||||
|
||||
private LuaTable wrapLuaObject( ILuaObject object )
|
||||
@Nullable
|
||||
private LuaTable wrapLuaObject( Object object )
|
||||
{
|
||||
String[] dynamicMethods = object instanceof IDynamicLuaObject
|
||||
? Objects.requireNonNull( ((IDynamicLuaObject) object).getMethodNames(), "Methods cannot be null" )
|
||||
: LuaMethod.EMPTY_METHODS;
|
||||
|
||||
LuaTable table = new LuaTable();
|
||||
String[] methods = object.getMethodNames();
|
||||
for( int i = 0; i < methods.length; i++ )
|
||||
for( int i = 0; i < dynamicMethods.length; i++ )
|
||||
{
|
||||
if( methods[i] != null )
|
||||
{
|
||||
final int method = i;
|
||||
final ILuaObject apiObject = object;
|
||||
final String methodName = methods[i];
|
||||
table.rawset( methodName, new VarArgFunction()
|
||||
{
|
||||
@Override
|
||||
public Varargs invoke( final LuaState state, Varargs args ) throws LuaError
|
||||
{
|
||||
Object[] arguments = toObjects( args, 1 );
|
||||
Object[] results;
|
||||
try
|
||||
{
|
||||
results = apiObject.callMethod( context, method, arguments );
|
||||
}
|
||||
catch( InterruptedException e )
|
||||
{
|
||||
throw new InterruptedError( e );
|
||||
}
|
||||
catch( LuaException e )
|
||||
{
|
||||
throw new LuaError( e.getMessage(), e.getLevel() );
|
||||
}
|
||||
catch( Throwable t )
|
||||
{
|
||||
if( ComputerCraft.logPeripheralErrors )
|
||||
{
|
||||
ComputerCraft.log.error( "Error calling " + methodName + " on " + apiObject, t );
|
||||
}
|
||||
throw new LuaError( "Java Exception Thrown: " + t, 0 );
|
||||
}
|
||||
return toValues( results );
|
||||
}
|
||||
} );
|
||||
}
|
||||
String method = dynamicMethods[i];
|
||||
table.rawset( method, new ResultInterpreterFunction( this, LuaMethod.DYNAMIC.get( i ), object, context, method ) );
|
||||
}
|
||||
|
||||
ObjectSource.allMethods( LuaMethod.GENERATOR, object, ( instance, method ) ->
|
||||
table.rawset( method.getName(), method.nonYielding()
|
||||
? new BasicFunction( this, method.getMethod(), instance, context, method.getName() )
|
||||
: new ResultInterpreterFunction( this, method.getMethod(), instance, context, method.getName() ) ) );
|
||||
|
||||
try
|
||||
{
|
||||
if( table.keyCount() == 0 ) return null;
|
||||
}
|
||||
catch( LuaError ignored )
|
||||
{
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private LuaValue toValue( @Nullable Object object, @Nonnull Map<Object, LuaValue> values )
|
||||
private LuaValue toValue( @Nullable Object object, @Nullable Map<Object, LuaValue> values )
|
||||
{
|
||||
if( object == null ) return Constants.NIL;
|
||||
if( object instanceof Number ) return valueOf( ((Number) object).doubleValue() );
|
||||
@@ -274,13 +263,22 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
byte[] b = (byte[]) object;
|
||||
return valueOf( Arrays.copyOf( b, b.length ) );
|
||||
}
|
||||
if( object instanceof ByteBuffer )
|
||||
{
|
||||
ByteBuffer b = (ByteBuffer) object;
|
||||
byte[] bytes = new byte[b.remaining()];
|
||||
b.get( bytes );
|
||||
return valueOf( bytes );
|
||||
}
|
||||
|
||||
if( values == null ) values = new IdentityHashMap<>( 1 );
|
||||
LuaValue result = values.get( object );
|
||||
if( result != null ) return result;
|
||||
|
||||
if( object instanceof ILuaObject )
|
||||
if( object instanceof IDynamicLuaObject )
|
||||
{
|
||||
LuaValue wrapped = wrapLuaObject( (ILuaObject) object );
|
||||
LuaValue wrapped = wrapLuaObject( object );
|
||||
if( wrapped == null ) wrapped = new LuaTable();
|
||||
values.put( object, wrapped );
|
||||
return wrapped;
|
||||
}
|
||||
@@ -318,16 +316,24 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
return table;
|
||||
}
|
||||
|
||||
if( ComputerCraft.logPeripheralErrors )
|
||||
LuaTable wrapped = wrapLuaObject( object );
|
||||
if( wrapped != null )
|
||||
{
|
||||
values.put( object, wrapped );
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
if( ComputerCraft.logComputerErrors )
|
||||
{
|
||||
ComputerCraft.log.warn( "Received unknown type '{}', returning nil.", object.getClass().getName() );
|
||||
}
|
||||
return Constants.NIL;
|
||||
}
|
||||
|
||||
private Varargs toValues( Object[] objects )
|
||||
Varargs toValues( Object[] objects )
|
||||
{
|
||||
if( objects == null || objects.length == 0 ) return Constants.NONE;
|
||||
if( objects.length == 1 ) return toValue( objects[0], null );
|
||||
|
||||
Map<Object, LuaValue> result = new IdentityHashMap<>( 0 );
|
||||
LuaValue[] values = new LuaValue[objects.length];
|
||||
@@ -339,7 +345,7 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
return varargsOf( values );
|
||||
}
|
||||
|
||||
private static Object toObject( LuaValue value, Map<LuaValue, Object> objects )
|
||||
static Object toObject( LuaValue value, Map<LuaValue, Object> objects )
|
||||
{
|
||||
switch( value.type() )
|
||||
{
|
||||
@@ -359,11 +365,12 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
// Start remembering stuff
|
||||
if( objects == null )
|
||||
{
|
||||
objects = new IdentityHashMap<>();
|
||||
objects = new IdentityHashMap<>( 1 );
|
||||
}
|
||||
else if( objects.containsKey( value ) )
|
||||
else
|
||||
{
|
||||
return objects.get( value );
|
||||
Object existing = objects.get( value );
|
||||
if( existing != null ) return existing;
|
||||
}
|
||||
Map<Object, Object> table = new HashMap<>();
|
||||
objects.put( value, table );
|
||||
@@ -384,10 +391,7 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
break;
|
||||
}
|
||||
k = keyValue.first();
|
||||
if( k.isNil() )
|
||||
{
|
||||
break;
|
||||
}
|
||||
if( k.isNil() ) break;
|
||||
|
||||
LuaValue v = keyValue.arg( 2 );
|
||||
Object keyObject = toObject( k, objects );
|
||||
@@ -404,19 +408,19 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
}
|
||||
}
|
||||
|
||||
private static Object[] toObjects( Varargs values, int startIdx )
|
||||
static Object[] toObjects( Varargs values )
|
||||
{
|
||||
int count = values.count();
|
||||
Object[] objects = new Object[count - startIdx + 1];
|
||||
for( int n = startIdx; n <= count; n++ )
|
||||
{
|
||||
int i = n - startIdx;
|
||||
LuaValue value = values.arg( n );
|
||||
objects[i] = toObject( value, null );
|
||||
}
|
||||
Object[] objects = new Object[count];
|
||||
for( int i = 0; i < count; i++ ) objects[i] = toObject( values.arg( i + 1 ), null );
|
||||
return objects;
|
||||
}
|
||||
|
||||
static IArguments toArguments( Varargs values )
|
||||
{
|
||||
return values == Constants.NONE ? VarargArguments.EMPTY : new VarargArguments( values );
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link DebugHandler} which observes the {@link TimeoutState} and responds accordingly.
|
||||
*/
|
||||
@@ -500,23 +504,6 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
|
||||
private class CobaltLuaContext implements ILuaContext
|
||||
{
|
||||
@Nonnull
|
||||
@Override
|
||||
public Object[] yield( Object[] yieldArgs ) throws InterruptedException
|
||||
{
|
||||
try
|
||||
{
|
||||
LuaState state = m_state;
|
||||
if( state == null ) throw new InterruptedException();
|
||||
Varargs results = LuaThread.yieldBlocking( state, toValues( yieldArgs ) );
|
||||
return toObjects( results, 1 );
|
||||
}
|
||||
catch( LuaError e )
|
||||
{
|
||||
throw new IllegalStateException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long issueMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException
|
||||
{
|
||||
@@ -545,7 +532,7 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
}
|
||||
catch( Throwable t )
|
||||
{
|
||||
if( ComputerCraft.logPeripheralErrors ) ComputerCraft.log.error( "Error running task", t );
|
||||
if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error running task", t );
|
||||
m_computer.queueEvent( "task_complete", new Object[] {
|
||||
taskID, false, "Java Exception Thrown: " + t,
|
||||
} );
|
||||
@@ -560,45 +547,6 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
throw new LuaException( "Task limit exceeded" );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] executeMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException, InterruptedException
|
||||
{
|
||||
// Issue task
|
||||
final long taskID = issueMainThreadTask( task );
|
||||
|
||||
// Wait for response
|
||||
while( true )
|
||||
{
|
||||
Object[] response = pullEvent( "task_complete" );
|
||||
if( response.length >= 3 && response[1] instanceof Number && response[2] instanceof Boolean )
|
||||
{
|
||||
if( ((Number) response[1]).intValue() == taskID )
|
||||
{
|
||||
Object[] returnValues = new Object[response.length - 3];
|
||||
if( (Boolean) response[2] )
|
||||
{
|
||||
// Extract the return values from the event and return them
|
||||
System.arraycopy( response, 3, returnValues, 0, returnValues.length );
|
||||
return returnValues;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Extract the error message from the event and raise it
|
||||
if( response.length >= 4 && response[3] instanceof String )
|
||||
{
|
||||
throw new LuaException( (String) response[3] );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new LuaException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static final class HardAbortError extends Error
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
*/
|
||||
package dan200.computercraft.core.lua;
|
||||
|
||||
import dan200.computercraft.api.lua.IDynamicLuaObject;
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaObject;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -21,13 +21,13 @@ import java.io.InputStream;
|
||||
* mechanism for registering these.
|
||||
*
|
||||
* This should provide implementations of {@link dan200.computercraft.api.lua.ILuaContext}, and the ability to convert
|
||||
* {@link ILuaObject}s into something the VM understands, as well as handling method calls.
|
||||
* {@link IDynamicLuaObject}s into something the VM understands, as well as handling method calls.
|
||||
*/
|
||||
public interface ILuaMachine
|
||||
{
|
||||
/**
|
||||
* Inject an API into the global environment of this machine. This should construct an object, as it would for any
|
||||
* {@link ILuaObject} and set it to all names in {@link ILuaAPI#getNames()}.
|
||||
* {@link IDynamicLuaObject} and set it to all names in {@link ILuaAPI#getNames()}.
|
||||
*
|
||||
* Called before {@link #loadBios(InputStream)}.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.lua;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.*;
|
||||
import dan200.computercraft.core.asm.LuaMethod;
|
||||
import org.squiddev.cobalt.*;
|
||||
import org.squiddev.cobalt.debug.DebugFrame;
|
||||
import org.squiddev.cobalt.function.ResumableVarArgFunction;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Calls a {@link LuaMethod}, and interprets the resulting {@link MethodResult}, either returning the result or yielding
|
||||
* and resuming the supplied continuation.
|
||||
*/
|
||||
class ResultInterpreterFunction extends ResumableVarArgFunction<ResultInterpreterFunction.Container>
|
||||
{
|
||||
@Nonnull
|
||||
static class Container
|
||||
{
|
||||
ILuaCallback callback;
|
||||
int errorAdjust;
|
||||
|
||||
Container( ILuaCallback callback, int errorAdjust )
|
||||
{
|
||||
this.callback = callback;
|
||||
this.errorAdjust = errorAdjust;
|
||||
}
|
||||
}
|
||||
|
||||
private final CobaltLuaMachine machine;
|
||||
private final LuaMethod method;
|
||||
private final Object instance;
|
||||
private final ILuaContext context;
|
||||
private final String name;
|
||||
|
||||
ResultInterpreterFunction( CobaltLuaMachine machine, LuaMethod method, Object instance, ILuaContext context, String name )
|
||||
{
|
||||
this.machine = machine;
|
||||
this.method = method;
|
||||
this.instance = instance;
|
||||
this.context = context;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Varargs invoke( LuaState state, DebugFrame debugFrame, Varargs args ) throws LuaError, UnwindThrowable
|
||||
{
|
||||
IArguments arguments = CobaltLuaMachine.toArguments( args );
|
||||
MethodResult results;
|
||||
try
|
||||
{
|
||||
results = method.apply( instance, context, arguments );
|
||||
}
|
||||
catch( LuaException e )
|
||||
{
|
||||
throw wrap( e, 0 );
|
||||
}
|
||||
catch( Throwable t )
|
||||
{
|
||||
if( ComputerCraft.logComputerErrors )
|
||||
{
|
||||
ComputerCraft.log.error( "Error calling " + name + " on " + instance, t );
|
||||
}
|
||||
throw new LuaError( "Java Exception Thrown: " + t, 0 );
|
||||
}
|
||||
|
||||
ILuaCallback callback = results.getCallback();
|
||||
Varargs ret = machine.toValues( results.getResult() );
|
||||
|
||||
if( callback == null ) return ret;
|
||||
|
||||
debugFrame.state = new Container( callback, results.getErrorAdjust() );
|
||||
return LuaThread.yield( state, ret );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Varargs resumeThis( LuaState state, Container container, Varargs args ) throws LuaError, UnwindThrowable
|
||||
{
|
||||
MethodResult results;
|
||||
Object[] arguments = CobaltLuaMachine.toObjects( args );
|
||||
try
|
||||
{
|
||||
results = container.callback.resume( arguments );
|
||||
}
|
||||
catch( LuaException e )
|
||||
{
|
||||
throw wrap( e, container.errorAdjust );
|
||||
}
|
||||
catch( Throwable t )
|
||||
{
|
||||
if( ComputerCraft.logComputerErrors )
|
||||
{
|
||||
ComputerCraft.log.error( "Error calling " + name + " on " + container.callback, t );
|
||||
}
|
||||
throw new LuaError( "Java Exception Thrown: " + t, 0 );
|
||||
}
|
||||
|
||||
Varargs ret = machine.toValues( results.getResult() );
|
||||
|
||||
ILuaCallback callback = results.getCallback();
|
||||
if( callback == null ) return ret;
|
||||
|
||||
container.callback = callback;
|
||||
return LuaThread.yield( state, ret );
|
||||
}
|
||||
|
||||
public static LuaError wrap( LuaException exception, int adjust )
|
||||
{
|
||||
if( !exception.hasLevel() && adjust == 0 ) return new LuaError( exception.getMessage() );
|
||||
|
||||
int level = exception.getLevel();
|
||||
return new LuaError( exception.getMessage(), level <= 0 ? level : level + adjust + 1 );
|
||||
}
|
||||
}
|
||||
102
src/main/java/dan200/computercraft/core/lua/VarargArguments.java
Normal file
102
src/main/java/dan200/computercraft/core/lua/VarargArguments.java
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.lua;
|
||||
|
||||
import dan200.computercraft.api.lua.IArguments;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaValues;
|
||||
import org.squiddev.cobalt.*;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Optional;
|
||||
|
||||
class VarargArguments implements IArguments
|
||||
{
|
||||
static final IArguments EMPTY = new VarargArguments( Constants.NONE );
|
||||
|
||||
private final Varargs varargs;
|
||||
private Object[] cache;
|
||||
|
||||
VarargArguments( Varargs varargs )
|
||||
{
|
||||
this.varargs = varargs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count()
|
||||
{
|
||||
return varargs.count();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object get( int index )
|
||||
{
|
||||
if( index < 0 || index >= varargs.count() ) return null;
|
||||
|
||||
Object[] cache = this.cache;
|
||||
if( cache == null )
|
||||
{
|
||||
cache = this.cache = new Object[varargs.count()];
|
||||
}
|
||||
else
|
||||
{
|
||||
Object existing = cache[index];
|
||||
if( existing != null ) return existing;
|
||||
}
|
||||
|
||||
return cache[index] = CobaltLuaMachine.toObject( varargs.arg( index + 1 ), null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public IArguments drop( int count )
|
||||
{
|
||||
if( count < 0 ) throw new IllegalStateException( "count cannot be negative" );
|
||||
if( count == 0 ) return this;
|
||||
return new VarargArguments( varargs.subargs( count + 1 ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDouble( int index ) throws LuaException
|
||||
{
|
||||
LuaValue value = varargs.arg( index + 1 );
|
||||
if( !(value instanceof LuaNumber) ) throw LuaValues.badArgument( index, "number", value.typeName() );
|
||||
return value.toDouble();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong( int index ) throws LuaException
|
||||
{
|
||||
LuaValue value = varargs.arg( index + 1 );
|
||||
if( !(value instanceof LuaNumber) ) throw LuaValues.badArgument( index, "number", value.typeName() );
|
||||
return value instanceof LuaInteger ? value.toInteger() : (long) LuaValues.checkFinite( index, value.toDouble() );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public ByteBuffer getBytes( int index ) throws LuaException
|
||||
{
|
||||
LuaValue value = varargs.arg( index + 1 );
|
||||
if( !(value instanceof LuaString) ) throw LuaValues.badArgument( index, "string", value.typeName() );
|
||||
|
||||
LuaString str = (LuaString) value;
|
||||
return ByteBuffer.wrap( str.bytes, str.offset, str.length ).asReadOnlyBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ByteBuffer> optBytes( int index ) throws LuaException
|
||||
{
|
||||
LuaValue value = varargs.arg( index + 1 );
|
||||
if( value.isNil() ) return Optional.empty();
|
||||
if( !(value instanceof LuaString) ) throw LuaValues.badArgument( index, "string", value.typeName() );
|
||||
|
||||
LuaString str = (LuaString) value;
|
||||
return Optional.of( ByteBuffer.wrap( str.bytes, str.offset, str.length ).asReadOnlyBuffer() );
|
||||
}
|
||||
}
|
||||
@@ -5,18 +5,20 @@
|
||||
*/
|
||||
package dan200.computercraft.core.terminal;
|
||||
|
||||
import dan200.computercraft.shared.util.Colour;
|
||||
import dan200.computercraft.shared.util.Palette;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
|
||||
public class Terminal
|
||||
{
|
||||
private static final String base16 = "0123456789abcdef";
|
||||
|
||||
private int m_cursorX;
|
||||
private int m_cursorY;
|
||||
private boolean m_cursorBlink;
|
||||
private int m_cursorColour;
|
||||
private int m_cursorBackgroundColour;
|
||||
private int m_cursorX = 0;
|
||||
private int m_cursorY = 0;
|
||||
private boolean m_cursorBlink = false;
|
||||
private int m_cursorColour = 0;
|
||||
private int m_cursorBackgroundColour = 15;
|
||||
|
||||
private int m_width;
|
||||
private int m_height;
|
||||
@@ -25,9 +27,9 @@ public class Terminal
|
||||
private TextBuffer[] m_textColour;
|
||||
private TextBuffer[] m_backgroundColour;
|
||||
|
||||
private final Palette m_palette;
|
||||
private final Palette m_palette = new Palette();
|
||||
|
||||
private boolean m_changed;
|
||||
private boolean m_changed = false;
|
||||
private final Runnable onChanged;
|
||||
|
||||
public Terminal( int width, int height )
|
||||
@@ -41,9 +43,6 @@ public class Terminal
|
||||
m_height = height;
|
||||
onChanged = changedCallback;
|
||||
|
||||
m_cursorColour = 0;
|
||||
m_cursorBackgroundColour = 15;
|
||||
|
||||
m_text = new TextBuffer[m_height];
|
||||
m_textColour = new TextBuffer[m_height];
|
||||
m_backgroundColour = new TextBuffer[m_height];
|
||||
@@ -53,14 +52,6 @@ public class Terminal
|
||||
m_textColour[i] = new TextBuffer( base16.charAt( m_cursorColour ), m_width );
|
||||
m_backgroundColour[i] = new TextBuffer( base16.charAt( m_cursorBackgroundColour ), m_width );
|
||||
}
|
||||
|
||||
m_cursorX = 0;
|
||||
m_cursorY = 0;
|
||||
m_cursorBlink = false;
|
||||
|
||||
m_changed = false;
|
||||
|
||||
m_palette = new Palette();
|
||||
}
|
||||
|
||||
public synchronized void reset()
|
||||
@@ -323,6 +314,62 @@ public class Terminal
|
||||
m_changed = false;
|
||||
}
|
||||
|
||||
public synchronized void write( PacketBuffer buffer )
|
||||
{
|
||||
buffer.writeInt( m_cursorX );
|
||||
buffer.writeInt( m_cursorY );
|
||||
buffer.writeBoolean( m_cursorBlink );
|
||||
buffer.writeByte( m_cursorBackgroundColour << 4 | m_cursorColour );
|
||||
|
||||
for( int y = 0; y < m_height; y++ )
|
||||
{
|
||||
TextBuffer text = m_text[y];
|
||||
TextBuffer textColour = m_textColour[y];
|
||||
TextBuffer backColour = m_backgroundColour[y];
|
||||
|
||||
for( int x = 0; x < m_width; x++ )
|
||||
{
|
||||
buffer.writeByte( text.charAt( x ) & 0xFF );
|
||||
buffer.writeByte( getColour(
|
||||
backColour.charAt( x ), Colour.BLACK ) << 4 |
|
||||
getColour( textColour.charAt( x ), Colour.WHITE )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
m_palette.write( buffer );
|
||||
}
|
||||
|
||||
public synchronized void read( PacketBuffer buffer )
|
||||
{
|
||||
m_cursorX = buffer.readInt();
|
||||
m_cursorY = buffer.readInt();
|
||||
m_cursorBlink = buffer.readBoolean();
|
||||
|
||||
byte cursorColour = buffer.readByte();
|
||||
m_cursorBackgroundColour = (cursorColour >> 4) & 0xF;
|
||||
m_cursorColour = cursorColour & 0xF;
|
||||
|
||||
for( int y = 0; y < m_height; y++ )
|
||||
{
|
||||
TextBuffer text = m_text[y];
|
||||
TextBuffer textColour = m_textColour[y];
|
||||
TextBuffer backColour = m_backgroundColour[y];
|
||||
|
||||
for( int x = 0; x < m_width; x++ )
|
||||
{
|
||||
text.setChar( x, (char) (buffer.readByte() & 0xFF) );
|
||||
|
||||
byte colour = buffer.readByte();
|
||||
backColour.setChar( x, base16.charAt( (colour >> 4) & 0xF ) );
|
||||
textColour.setChar( x, base16.charAt( colour & 0xF ) );
|
||||
}
|
||||
}
|
||||
|
||||
m_palette.read( buffer );
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public synchronized CompoundNBT writeToNBT( CompoundNBT nbt )
|
||||
{
|
||||
nbt.putInt( "term_cursorX", m_cursorX );
|
||||
@@ -336,10 +383,8 @@ public class Terminal
|
||||
nbt.putString( "term_textColour_" + n, m_textColour[n].toString() );
|
||||
nbt.putString( "term_textBgColour_" + n, m_backgroundColour[n].toString() );
|
||||
}
|
||||
if( m_palette != null )
|
||||
{
|
||||
m_palette.writeToNBT( nbt );
|
||||
}
|
||||
|
||||
m_palette.writeToNBT( nbt );
|
||||
return nbt;
|
||||
}
|
||||
|
||||
@@ -369,10 +414,15 @@ public class Terminal
|
||||
m_backgroundColour[n].write( nbt.getString( "term_textBgColour_" + n ) );
|
||||
}
|
||||
}
|
||||
if( m_palette != null )
|
||||
{
|
||||
m_palette.readFromNBT( nbt );
|
||||
}
|
||||
|
||||
m_palette.readFromNBT( nbt );
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public static int getColour( char c, Colour def )
|
||||
{
|
||||
if( c >= '0' && c <= '9' ) return c - '0';
|
||||
if( c >= 'a' && c <= 'f' ) return c - 'a' + 10;
|
||||
return 15 - def.ordinal();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,13 +47,13 @@ public abstract class LootTableProvider implements IDataProvider
|
||||
ValidationTracker validation = new ValidationTracker( LootParameterSets.GENERIC, x -> null, tables::get );
|
||||
|
||||
registerLoot( ( id, table ) -> {
|
||||
if( tables.containsKey( id ) ) validation.func_227530_a_( "Duplicate loot tables for " + id );
|
||||
if( tables.containsKey( id ) ) validation.addProblem( "Duplicate loot tables for " + id );
|
||||
tables.put( id, table );
|
||||
} );
|
||||
|
||||
tables.forEach( ( key, value ) -> LootTableManager.func_227508_a_( validation, key, value ) );
|
||||
|
||||
Multimap<String, String> problems = validation.func_227527_a_();
|
||||
Multimap<String, String> problems = validation.getProblems();
|
||||
if( !problems.isEmpty() )
|
||||
{
|
||||
problems.forEach( ( child, problem ) ->
|
||||
|
||||
@@ -8,7 +8,9 @@ package dan200.computercraft.data;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
|
||||
import dan200.computercraft.shared.data.HasComputerIdLootCondition;
|
||||
import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
|
||||
import dan200.computercraft.shared.proxy.ComputerCraftProxyCommon;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.data.DataGenerator;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
@@ -41,6 +43,11 @@ public class LootTables extends LootTableProvider
|
||||
computerDrop( add, ComputerCraft.Blocks.computerAdvanced );
|
||||
computerDrop( add, ComputerCraft.Blocks.turtleNormal );
|
||||
computerDrop( add, ComputerCraft.Blocks.turtleAdvanced );
|
||||
|
||||
add.accept( ComputerCraftProxyCommon.ForgeHandlers.LOOT_TREASURE_DISK, LootTable
|
||||
.builder()
|
||||
.setParameterSet( LootParameterSets.GENERIC )
|
||||
.build() );
|
||||
}
|
||||
|
||||
private static void basicDrop( BiConsumer<ResourceLocation, LootTable> add, Block block )
|
||||
@@ -67,6 +74,7 @@ public class LootTables extends LootTableProvider
|
||||
.addEntry( DynamicLootEntry.func_216162_a( new ResourceLocation( ComputerCraft.MOD_ID, "computer" ) ) )
|
||||
.acceptCondition( Alternative.builder(
|
||||
BlockNamedEntityLootCondition.builder(),
|
||||
HasComputerIdLootCondition.builder(),
|
||||
PlayerCreativeLootCondition.builder().inverted()
|
||||
) )
|
||||
).build() );
|
||||
|
||||
25
src/main/java/dan200/computercraft/shared/Capabilities.java
Normal file
25
src/main/java/dan200/computercraft/shared/Capabilities.java
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.shared;
|
||||
|
||||
import dan200.computercraft.api.network.wired.IWiredElement;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
import net.minecraftforge.common.capabilities.CapabilityInject;
|
||||
|
||||
public final class Capabilities
|
||||
{
|
||||
@CapabilityInject( IPeripheral.class )
|
||||
public static Capability<IPeripheral> CAPABILITY_PERIPHERAL = null;
|
||||
|
||||
@CapabilityInject( IWiredElement.class )
|
||||
public static Capability<IWiredElement> CAPABILITY_WIRED_ELEMENT = null;
|
||||
|
||||
private Capabilities()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -12,15 +12,15 @@ import com.google.common.base.CaseFormat;
|
||||
import com.google.common.base.Converter;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.turtle.event.TurtleAction;
|
||||
import dan200.computercraft.core.apis.http.AddressRule;
|
||||
import dan200.computercraft.core.apis.http.websocket.Websocket;
|
||||
import dan200.computercraft.core.apis.http.options.Action;
|
||||
import dan200.computercraft.core.apis.http.options.AddressRuleConfig;
|
||||
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
|
||||
import net.minecraftforge.common.ForgeConfigSpec;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.ModLoadingContext;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@@ -54,12 +54,8 @@ public final class Config
|
||||
private static final ConfigValue<Boolean> httpWebsocketEnabled;
|
||||
private static final ConfigValue<List<? extends UnmodifiableConfig>> httpRules;
|
||||
|
||||
private static final ConfigValue<Integer> httpTimeout;
|
||||
private static final ConfigValue<Integer> httpMaxRequests;
|
||||
private static final ConfigValue<Integer> httpMaxDownload;
|
||||
private static final ConfigValue<Integer> httpMaxUpload;
|
||||
private static final ConfigValue<Integer> httpMaxWebsockets;
|
||||
private static final ConfigValue<Integer> httpMaxWebsocketMessage;
|
||||
|
||||
private static final ConfigValue<Boolean> commandBlockEnabled;
|
||||
private static final ConfigValue<Integer> modemRange;
|
||||
@@ -75,7 +71,10 @@ public final class Config
|
||||
private static final ConfigValue<Boolean> turtlesCanPush;
|
||||
private static final ConfigValue<List<? extends String>> turtleDisabledActions;
|
||||
|
||||
private static final ForgeConfigSpec spec;
|
||||
private static final ConfigValue<MonitorRenderer> monitorRenderer;
|
||||
|
||||
private static final ForgeConfigSpec serverSpec;
|
||||
private static final ForgeConfigSpec clientSpec;
|
||||
|
||||
private Config() {}
|
||||
|
||||
@@ -102,22 +101,22 @@ public final class Config
|
||||
disableLua51Features = builder
|
||||
.comment( "Set this to true to disable Lua 5.1 functions that will be removed in a future update. " +
|
||||
"Useful for ensuring forward compatibility of your programs now." )
|
||||
.define( "disable_lua51_features", ComputerCraft.disable_lua51_features );
|
||||
.define( "disable_lua51_features", ComputerCraft.disableLua51Features );
|
||||
|
||||
defaultComputerSettings = builder
|
||||
.comment( "A comma separated list of default system settings to set on new computers. Example: " +
|
||||
"\"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\" will disable all " +
|
||||
"autocompletion" )
|
||||
.define( "default_computer_settings", ComputerCraft.default_computer_settings );
|
||||
.define( "default_computer_settings", ComputerCraft.defaultComputerSettings );
|
||||
|
||||
debugEnabled = builder
|
||||
.comment( "Enable Lua's debug library. This is sandboxed to each computer, so is generally safe to be used by players." )
|
||||
.define( "debug_enabled", ComputerCraft.debug_enable );
|
||||
.define( "debug_enabled", ComputerCraft.debugEnable );
|
||||
|
||||
logComputerErrors = builder
|
||||
.comment( "Log exceptions thrown by peripherals and other Lua objects.\n" +
|
||||
"This makes it easier for mod authors to debug problems, but may result in log spam should people use buggy methods." )
|
||||
.define( "log_computer_errors", ComputerCraft.logPeripheralErrors );
|
||||
.define( "log_computer_errors", ComputerCraft.logComputerErrors );
|
||||
}
|
||||
|
||||
{
|
||||
@@ -130,7 +129,7 @@ public final class Config
|
||||
"at once, but may induce lag.\n" +
|
||||
"Please note that some mods may not work with a thread count higher than 1. Use with caution." )
|
||||
.worldRestart()
|
||||
.defineInRange( "computer_threads", ComputerCraft.computer_threads, 1, Integer.MAX_VALUE );
|
||||
.defineInRange( "computer_threads", ComputerCraft.computerThreads, 1, Integer.MAX_VALUE );
|
||||
|
||||
maxMainGlobalTime = builder
|
||||
.comment( "The maximum time that can be spent executing tasks in a single tick, in milliseconds.\n" +
|
||||
@@ -160,42 +159,25 @@ public final class Config
|
||||
.define( "websocket_enabled", ComputerCraft.httpWebsocketEnabled );
|
||||
|
||||
httpRules = builder
|
||||
.comment( "A list of rules which control which domains or IPs are allowed through the \"http\" API on computers.\n" +
|
||||
"Each rule is an item with a 'host' to match against, and an action. " +
|
||||
.comment( "A list of rules which control behaviour of the \"http\" API for specific domains or IPs.\n" +
|
||||
"Each rule is an item with a 'host' to match against, and a series of properties. " +
|
||||
"The host may be a domain name (\"pastebin.com\"),\n" +
|
||||
"wildcard (\"*.pastebin.com\") or CIDR notation (\"127.0.0.0/8\"). 'action' maybe 'allow' or 'block'. If no rules" +
|
||||
"match, the domain will be blocked." )
|
||||
"wildcard (\"*.pastebin.com\") or CIDR notation (\"127.0.0.0/8\"). If no rules, the domain is blocked." )
|
||||
.defineList( "rules",
|
||||
Stream.concat(
|
||||
Stream.of( ComputerCraft.DEFAULT_HTTP_DENY ).map( x -> makeRule( x, "deny" ) ),
|
||||
Stream.of( ComputerCraft.DEFAULT_HTTP_ALLOW ).map( x -> makeRule( x, "allow" ) )
|
||||
Stream.of( ComputerCraft.DEFAULT_HTTP_DENY ).map( x -> AddressRuleConfig.makeRule( x, Action.DENY ) ),
|
||||
Stream.of( ComputerCraft.DEFAULT_HTTP_ALLOW ).map( x -> AddressRuleConfig.makeRule( x, Action.ALLOW ) )
|
||||
).collect( Collectors.toList() ),
|
||||
x -> x instanceof UnmodifiableConfig && parseRule( (UnmodifiableConfig) x ) != null );
|
||||
|
||||
httpTimeout = builder
|
||||
.comment( "The period of time (in milliseconds) to wait before a HTTP request times out. Set to 0 for unlimited." )
|
||||
.defineInRange( "timeout", ComputerCraft.httpTimeout, 0, Integer.MAX_VALUE );
|
||||
x -> x instanceof UnmodifiableConfig && AddressRuleConfig.checkRule( (UnmodifiableConfig) x ) );
|
||||
|
||||
httpMaxRequests = builder
|
||||
.comment( "The number of http requests a computer can make at one time. Additional requests will be queued, and sent when the running requests have finished. Set to 0 for unlimited." )
|
||||
.defineInRange( "max_requests", ComputerCraft.httpMaxRequests, 0, Integer.MAX_VALUE );
|
||||
|
||||
httpMaxDownload = builder
|
||||
.comment( "The maximum size (in bytes) that a computer can download in a single request. Note that responses may receive more data than allowed, but this data will not be returned to the client." )
|
||||
.defineInRange( "max_download", (int) ComputerCraft.httpMaxDownload, 0, Integer.MAX_VALUE );
|
||||
|
||||
httpMaxUpload = builder
|
||||
.comment( "The maximum size (in bytes) that a computer can upload in a single request. This includes headers and POST text." )
|
||||
.defineInRange( "max_upload", (int) ComputerCraft.httpMaxUpload, 0, Integer.MAX_VALUE );
|
||||
|
||||
httpMaxWebsockets = builder
|
||||
.comment( "The number of websockets a computer can have open at one time. Set to 0 for unlimited." )
|
||||
.defineInRange( "max_websockets", ComputerCraft.httpMaxWebsockets, 1, Integer.MAX_VALUE );
|
||||
|
||||
httpMaxWebsocketMessage = builder
|
||||
.comment( "The maximum size (in bytes) that a computer can send or receive in one websocket packet." )
|
||||
.defineInRange( "max_websocket_message", ComputerCraft.httpMaxWebsocketMessage, 0, Websocket.MAX_MESSAGE_SIZE );
|
||||
|
||||
builder.pop();
|
||||
}
|
||||
|
||||
@@ -209,19 +191,19 @@ public final class Config
|
||||
|
||||
modemRange = builder
|
||||
.comment( "The range of Wireless Modems at low altitude in clear weather, in meters" )
|
||||
.defineInRange( "modem_range", ComputerCraft.modem_range, 0, MODEM_MAX_RANGE );
|
||||
.defineInRange( "modem_range", ComputerCraft.modemRange, 0, MODEM_MAX_RANGE );
|
||||
|
||||
modemHighAltitudeRange = builder
|
||||
.comment( "The range of Wireless Modems at maximum altitude in clear weather, in meters" )
|
||||
.defineInRange( "modem_high_altitude_range", ComputerCraft.modem_highAltitudeRange, 0, MODEM_MAX_RANGE );
|
||||
.defineInRange( "modem_high_altitude_range", ComputerCraft.modemHighAltitudeRange, 0, MODEM_MAX_RANGE );
|
||||
|
||||
modemRangeDuringStorm = builder
|
||||
.comment( "The range of Wireless Modems at low altitude in stormy weather, in meters" )
|
||||
.defineInRange( "modem_range_during_storm", ComputerCraft.modem_rangeDuringStorm, 0, MODEM_MAX_RANGE );
|
||||
.defineInRange( "modem_range_during_storm", ComputerCraft.modemRangeDuringStorm, 0, MODEM_MAX_RANGE );
|
||||
|
||||
modemHighAltitudeRangeDuringStorm = builder
|
||||
.comment( "The range of Wireless Modems at maximum altitude in stormy weather, in meters" )
|
||||
.defineInRange( "modem_high_altitude_range_during_storm", ComputerCraft.modem_highAltitudeRangeDuringStorm, 0, MODEM_MAX_RANGE );
|
||||
.defineInRange( "modem_high_altitude_range_during_storm", ComputerCraft.modemHighAltitudeRangeDuringStorm, 0, MODEM_MAX_RANGE );
|
||||
|
||||
maxNotesPerTick = builder
|
||||
.comment( "Maximum amount of notes a speaker can play at once" )
|
||||
@@ -261,12 +243,20 @@ public final class Config
|
||||
builder.pop();
|
||||
}
|
||||
|
||||
spec = builder.build();
|
||||
serverSpec = builder.build();
|
||||
|
||||
Builder clientBuilder = new Builder();
|
||||
monitorRenderer = clientBuilder
|
||||
.comment( "The renderer to use for monitors. Generally this should be kept at \"best\" - if " +
|
||||
"monitors have performance issues, you may wish to experiment with alternative renderers." )
|
||||
.defineEnum( "monitor_renderer", MonitorRenderer.BEST );
|
||||
clientSpec = clientBuilder.build();
|
||||
}
|
||||
|
||||
public static void load()
|
||||
{
|
||||
ModLoadingContext.get().registerConfig( ModConfig.Type.COMMON, spec );
|
||||
ModLoadingContext.get().registerConfig( ModConfig.Type.SERVER, serverSpec );
|
||||
ModLoadingContext.get().registerConfig( ModConfig.Type.CLIENT, clientSpec );
|
||||
}
|
||||
|
||||
public static void sync()
|
||||
@@ -275,14 +265,14 @@ public final class Config
|
||||
ComputerCraft.computerSpaceLimit = computerSpaceLimit.get();
|
||||
ComputerCraft.floppySpaceLimit = floppySpaceLimit.get();
|
||||
ComputerCraft.maximumFilesOpen = maximumFilesOpen.get();
|
||||
ComputerCraft.disable_lua51_features = disableLua51Features.get();
|
||||
ComputerCraft.default_computer_settings = defaultComputerSettings.get();
|
||||
ComputerCraft.debug_enable = debugEnabled.get();
|
||||
ComputerCraft.computer_threads = computerThreads.get();
|
||||
ComputerCraft.logPeripheralErrors = logComputerErrors.get();
|
||||
ComputerCraft.disableLua51Features = disableLua51Features.get();
|
||||
ComputerCraft.defaultComputerSettings = defaultComputerSettings.get();
|
||||
ComputerCraft.debugEnable = debugEnabled.get();
|
||||
ComputerCraft.computerThreads = computerThreads.get();
|
||||
ComputerCraft.logComputerErrors = logComputerErrors.get();
|
||||
|
||||
// Execution
|
||||
ComputerCraft.computer_threads = computerThreads.get();
|
||||
ComputerCraft.computerThreads = computerThreads.get();
|
||||
ComputerCraft.maxMainGlobalTime = TimeUnit.MILLISECONDS.toNanos( maxMainGlobalTime.get() );
|
||||
ComputerCraft.maxMainComputerTime = TimeUnit.MILLISECONDS.toNanos( maxMainComputerTime.get() );
|
||||
|
||||
@@ -290,22 +280,18 @@ public final class Config
|
||||
ComputerCraft.httpEnabled = httpEnabled.get();
|
||||
ComputerCraft.httpWebsocketEnabled = httpWebsocketEnabled.get();
|
||||
ComputerCraft.httpRules = Collections.unmodifiableList( httpRules.get().stream()
|
||||
.map( Config::parseRule ).filter( Objects::nonNull ).collect( Collectors.toList() ) );
|
||||
.map( AddressRuleConfig::parseRule ).filter( Objects::nonNull ).collect( Collectors.toList() ) );
|
||||
|
||||
ComputerCraft.httpTimeout = httpTimeout.get();
|
||||
ComputerCraft.httpMaxRequests = httpMaxRequests.get();
|
||||
ComputerCraft.httpMaxDownload = httpMaxDownload.get();
|
||||
ComputerCraft.httpMaxUpload = httpMaxUpload.get();
|
||||
ComputerCraft.httpMaxWebsockets = httpMaxWebsockets.get();
|
||||
ComputerCraft.httpMaxWebsocketMessage = httpMaxWebsocketMessage.get();
|
||||
|
||||
// Peripheral
|
||||
ComputerCraft.enableCommandBlock = commandBlockEnabled.get();
|
||||
ComputerCraft.maxNotesPerTick = maxNotesPerTick.get();
|
||||
ComputerCraft.modem_range = modemRange.get();
|
||||
ComputerCraft.modem_highAltitudeRange = modemHighAltitudeRange.get();
|
||||
ComputerCraft.modem_rangeDuringStorm = modemRangeDuringStorm.get();
|
||||
ComputerCraft.modem_highAltitudeRangeDuringStorm = modemHighAltitudeRangeDuringStorm.get();
|
||||
ComputerCraft.modemRange = modemRange.get();
|
||||
ComputerCraft.modemHighAltitudeRange = modemHighAltitudeRange.get();
|
||||
ComputerCraft.modemRangeDuringStorm = modemRangeDuringStorm.get();
|
||||
ComputerCraft.modemHighAltitudeRangeDuringStorm = modemHighAltitudeRangeDuringStorm.get();
|
||||
|
||||
// Turtles
|
||||
ComputerCraft.turtlesNeedFuel = turtlesNeedFuel.get();
|
||||
@@ -316,6 +302,9 @@ public final class Config
|
||||
|
||||
ComputerCraft.turtleDisabledActions.clear();
|
||||
for( String value : turtleDisabledActions.get() ) ComputerCraft.turtleDisabledActions.add( getAction( value ) );
|
||||
|
||||
// Client
|
||||
ComputerCraft.monitorRenderer = monitorRenderer.get();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
@@ -347,28 +336,4 @@ public final class Config
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static UnmodifiableConfig makeRule( String host, String action )
|
||||
{
|
||||
com.electronwill.nightconfig.core.Config config = com.electronwill.nightconfig.core.Config.inMemory();
|
||||
config.add( "host", host );
|
||||
config.add( "action", action );
|
||||
return config;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static AddressRule parseRule( UnmodifiableConfig builder )
|
||||
{
|
||||
Object hostObj = builder.get( "host" );
|
||||
Object actionObj = builder.get( "action" );
|
||||
if( !(hostObj instanceof String) || !(actionObj instanceof String) ) return null;
|
||||
|
||||
String host = (String) hostObj, action = (String) actionObj;
|
||||
for( AddressRule.Action candiate : AddressRule.Action.values() )
|
||||
{
|
||||
if( candiate.name().equalsIgnoreCase( action ) ) return AddressRule.parse( host, candiate );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,15 +8,22 @@ package dan200.computercraft.shared;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.peripheral.IPeripheralProvider;
|
||||
import dan200.computercraft.shared.util.CapabilityUtil;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.Direction;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
import net.minecraftforge.common.util.NonNullConsumer;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Objects;
|
||||
|
||||
import static dan200.computercraft.shared.Capabilities.CAPABILITY_PERIPHERAL;
|
||||
|
||||
public final class Peripherals
|
||||
{
|
||||
private static final Collection<IPeripheralProvider> providers = new LinkedHashSet<>();
|
||||
@@ -29,20 +36,29 @@ public final class Peripherals
|
||||
providers.add( provider );
|
||||
}
|
||||
|
||||
public static IPeripheral getPeripheral( World world, BlockPos pos, Direction side )
|
||||
@Nullable
|
||||
public static IPeripheral getPeripheral( World world, BlockPos pos, Direction side, NonNullConsumer<LazyOptional<IPeripheral>> invalidate )
|
||||
{
|
||||
return World.isValid( pos ) && !world.isRemote ? getPeripheralAt( world, pos, side ) : null;
|
||||
return World.isValid( pos ) && !world.isRemote ? getPeripheralAt( world, pos, side, invalidate ) : null;
|
||||
}
|
||||
|
||||
private static IPeripheral getPeripheralAt( World world, BlockPos pos, Direction side )
|
||||
@Nullable
|
||||
private static IPeripheral getPeripheralAt( World world, BlockPos pos, Direction side, NonNullConsumer<LazyOptional<IPeripheral>> invalidate )
|
||||
{
|
||||
TileEntity block = world.getTileEntity( pos );
|
||||
if( block != null )
|
||||
{
|
||||
LazyOptional<IPeripheral> peripheral = block.getCapability( CAPABILITY_PERIPHERAL, side );
|
||||
if( peripheral.isPresent() ) return CapabilityUtil.unwrap( peripheral, invalidate );
|
||||
}
|
||||
|
||||
// Try the handlers in order:
|
||||
for( IPeripheralProvider peripheralProvider : providers )
|
||||
{
|
||||
try
|
||||
{
|
||||
IPeripheral peripheral = peripheralProvider.getPeripheral( world, pos, side );
|
||||
if( peripheral != null ) return peripheral;
|
||||
LazyOptional<IPeripheral> peripheral = peripheralProvider.getPeripheral( world, pos, side );
|
||||
if( peripheral.isPresent() ) return CapabilityUtil.unwrap( peripheral, invalidate );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
|
||||
@@ -50,7 +50,7 @@ public abstract class BlockGeneric extends Block
|
||||
@Nonnull
|
||||
@Override
|
||||
@Deprecated
|
||||
public final ActionResultType onBlockActivated( BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit )
|
||||
public final ActionResultType onBlockActivated( @Nonnull BlockState state, World world, @Nonnull BlockPos pos, @Nonnull PlayerEntity player, @Nonnull Hand hand, @Nonnull BlockRayTraceResult hit )
|
||||
{
|
||||
TileEntity tile = world.getTileEntity( pos );
|
||||
return tile instanceof TileGeneric ? ((TileGeneric) tile).onActivate( player, hand, hit ) : ActionResultType.PASS;
|
||||
@@ -58,7 +58,7 @@ public abstract class BlockGeneric extends Block
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public final void neighborChanged( BlockState state, World world, BlockPos pos, Block neighbourBlock, BlockPos neighbourPos, boolean isMoving )
|
||||
public final void neighborChanged( @Nonnull BlockState state, World world, @Nonnull BlockPos pos, @Nonnull Block neighbourBlock, @Nonnull BlockPos neighbourPos, boolean isMoving )
|
||||
{
|
||||
TileEntity tile = world.getTileEntity( pos );
|
||||
if( tile instanceof TileGeneric ) ((TileGeneric) tile).onNeighbourChange( neighbourPos );
|
||||
@@ -73,7 +73,7 @@ public abstract class BlockGeneric extends Block
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void tick( BlockState state, ServerWorld world, BlockPos pos, Random rand )
|
||||
public void tick( @Nonnull BlockState state, ServerWorld world, @Nonnull BlockPos pos, @Nonnull Random rand )
|
||||
{
|
||||
TileEntity te = world.getTileEntity( pos );
|
||||
if( te instanceof TileGeneric ) ((TileGeneric) te).blockTick();
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
package dan200.computercraft.shared.common;
|
||||
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
|
||||
public class ClientTerminal implements ITerminal
|
||||
{
|
||||
@@ -53,7 +55,7 @@ public class ClientTerminal implements ITerminal
|
||||
{
|
||||
CompoundNBT terminal = nbt.getCompound( "terminal" );
|
||||
resizeTerminal( terminal.getInt( "term_width" ), terminal.getInt( "term_height" ) );
|
||||
m_terminal.readFromNBT( terminal );
|
||||
m_terminal.read( new PacketBuffer( Unpooled.wrappedBuffer( terminal.getByteArray( "term_contents" ) ) ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user