Opencomputers: Rework Redstone behavior

Created on 25 Sep 2017  路  11Comments  路  Source: MightyPirates/OpenComputers

As it is now the Redstone behavior doesn't allow tick precise control which is very sad considering that a computer build is a considerate investment in the early game.

So like like to change the behavior of the redstone api that makes tick precise control much easier and friendly for the game.

Instead of directly applying the redstone change you write the changes into a "virtual buffer" which gets:
Either applied automatically at the beginning off a new tick (<- this is mainly to provide backwards compability and not to break hundreds of scripts)
Or explicitly by calling redstone.apply() which will queue it for the next tick and only returns when the signals are applied. (<- means you have a indirect method for running on game ticks)

The benefit of doing so, besides tick precise control, is you can change multiple redstone signals at once and run a simple state machine where redstone signals are wildly changed, in example: for internal logic, but only applied once in a tick.

feature-accepted

Most helpful comment

Heads up -- I've built a solution for improved redstone control, I've tested it in 1.7.10, and I am currently working on merging it up to 1.12

This work will close this ticket, but may not be what everyone was expecting

I'll also do the work to describe the api in our docs, but here is a teaser

This change provides overrides for set* and get* redstone methods to allow you to optionally specify sides and colors. The less you specify, the more data you get or can set

The following are equivalent

component.redstone.setOutput( sides.right, 15 )
component.redstone.setOutput( { [sides.right] = 15 } )
component.redstone.setBundledOutput( sides.left, colors.blue, 100 )
component.redstone.setBundledOutput( sides.left,  { [colors.blue] = 100 } )
component.redstone.setBundledOutput( { [sides.left] = { [colors.blue] = 100 } } )
component.redstone.getBundledOutput( sides.left ) -- returns ALL left side data
component.redstone.getBundledOutput() -- returns everything current set for output
component.redstone.getBundledInput()

This allows setting multiple values in a single call, with the same call cost as any other set call (and not the combined set cost of doing these individually)

All 11 comments

This seems like a good idea! I'm not sure about the ideal API, though.

... WTF why the hell isn't the redstone delay documented in the component api that would have saved me a lot of frustration.
I can live with the current standard delay of two ticks (should still be one) but that doesn't help when i need to set multiple different signals at once.
(And no, threads are not an option for micro controllers)

I'm all in favor of proper restrictions to prevent spamming public servers so I like to make a different approach:
The function redstone.setOutput() should accept a table containing only six values either a int or nil.
The fun part about this is since the sides are a enum anyway you can write stuff like

rs.setOutput( {[sides.top]=5, [sides.east]=0, [sides.negy]=15 })

and it would work fine because lua 馃憤

Wireless/bundled stuff may benefit from a table approach too.

@MrFaul That would be SO helpful for something I am planning to do! Not sure why this isn't a thing already.

i feel we should try to avoid supporting tick-precise networks from oc, this isn't a real time system and we've resisted acting as such. I like the idea of an improved redstone card with buffering, but that is out of scope for us. perhaps a good add-on card

Uh, I think this is valuable, not to be tick-precise per-say, but to set the states all at the same time, this is quite common in electronics and given how redstone is outside of the mod, It would probably be worth while to support. I like the idea of supporting an array as it will change the interface the least

fair enough

I have no clue how the redstone behavior is implemented in oc in general, but calling it out right "out of scope" rings my "Oh my fu**ing god, sounds like it is overly complicated and inefficient" bells...
(I don't mean that offensive its more a work habit related thing )

Could you give me a simplified explanation how redstone is handled by oc?

Btw. the "isn't a realtime system" argument isn't really valid since I'm able run most of my scripts multiple times in one tick if the need arises...
So asking for tick precise redstone behavior isn't a long shot at all.
Even vanilla stuff is very sensitiv in that regard so why the heck is a expensiv computer unable to do that.

"isn't a realtime system"

That is that you are no guaranteed at any point by OC to execute within a specific tick in MC. There is no way to force or otherwise contain execution to a tick and there will be no effort for the mod to do so. OC runs the lua machines in threads that are forced to yeild from time to time, one or more threads are picked to run and after a time forced to yeild again. This is intented to ensure that OC won't burden the tick in MC.

We can however change the setOutput behavior so that you can set the state of multiple redstone lines at once, this would be passed to the main thread at the same time and would ensure they all update on the same tick. In my head, this should solve your problem and allow the t2 card to more effectively resemble any electronics digital IO where an entire register can update on the same cycle

I know that, Lua is just fast enough that it is most cases sufficient for tick precise control.
(I have a scheduler for that and it works fine)
Of course, you have bad luck if you are on a crowded server with a lot off bad scripts which hog cpu time.
But honestly those are not the servers I play on so I couldn't care less.

Therefor a register system which pre-fetches all rs states (or better changes) to apply them once per tick for all oc blocks would be nice and probably a performance gain.

I have a suggestion for a tick-precise bulk Redstone IO system, intended for Tier 2 Redstone cards, that is inspired by a real scientific lab equipment ADC/DAC-device I recently worked with.
_- Wall of text incoming! -_

Bulk Redstone-IO for a device (like Tier 2 Redstone card) would be handled through the following functions (that would be available through the RS-IO component in addition to the existing ones):

  • initBulkIO(table:inputSides, table:outputSides, num:sampleCount, num:interval) initializes bulk IO on the Redstone-IO device and tells it which sides should be used for input/output channels as well as the buffer size (sampleCount) and the interval in seconds (or ticks) between samples.
  • setOutputBuffer(num:channel, table:data) sets the series of redstone values that should be emitted on the previously configured output channel with side given by outputSides[channel]. The table data should contain numerical values representing redstone strength values in its indices 0 through sampleCount-1. (this function would need to be called for every configured output side)
  • startBulkIO(bool:singleRun, num:startTime) will start the signal recording/playback on all configured sides with swapping the IO-buffers and reading/writing the first sample exactly in the tick where minecraft's total world time reaches startTime (or immediately if that tick has already been in the past). If singleRun is true then this will only run for the configured amount of samples and then stop, otherwise it will continue by swapping the IO-buffers every time all the samples in the buffers have been processed and automatically start a new cycle. I also sends a buffer_swap-event to the computer every time a new cycle has started/completed.
  • stopBulkIO(bool:force) stops the running bulk IO process. If force is true it will immediately stop and swap the IO-buffers (retrieving only the data that was read up to that point), otherwise it will let the current cycle finish and stop then.
  • getInputBuffer(num:channel):table similar to setOutputBuffer this returns the sample data previously read from the input side given by inputSides[channel]. (this function would need to be called for every configured input side)
  • disableBulkIO() shuts down bulk IO to make the used sides available for regular direct read/write operations again. This also deletes all the buffers.

Optional functions one might also want to add:

  • isBulkIOenabled():bool returns whether bulk IO is currently enabled on the device
  • getBulkIOconfig():table returns the current configuration for IO-sides, sample count and interval.
  • isBulkIOrunning():bool return whether signal recording/playback is in progress.
  • getOutputBuffer(num:channel):table returns the sample data currently scheduled for writing. (similar to getRedstoneOutput)
  • awaitBufferSwap():bool just as a handy short version for os.pullEvent("buffer_swap") to let the program halt until the next IO buffer swap happened.

How it would be used

This is an example on how one might emit a continuous tick precise signal using this proposed feature:

...
rs.initBulkIO({}, {sides.back}, 20, 0.05) --initialize output on back side, buffer size: 20 samples @ 1 sample per tick
rs.setOutputBuffer(0, nextSequenceChunk()) --prepare the first chunk of signal data
rs.startBulkIO(false, 0) --start playback immediately
while true
  ev = os.pullEvent()
  if ev == "buffer_swap" then
    rs.setOutputBuffer(0, nextSequenceChunk()) --feed in next chunk of signal data 
  elseif ev = ... then --some event telling us to stop
    break
  end
end
rs.stopBulkIO(false) --schedule stop of playback 
os.pullEvent("buffer_swap") --wait for last chunk to complete sending
rs.disableBulkIO() --cleanup

Or send a redstone signal sequence that repeats forever (without further program intervention):

rs.initBulkIO({}, {sides.back}, 8, 0.5)
rs.setOutputBuffer(0, {0, 15, 15, 0, 15, 0, 0, 15}) -- set our sequence
rs.startBulkIO(false, 0) --let it run in repeat

Implementation Idea

The whole thing uses a double buffering concept:
One set of buffers is accessible to the user program via the get and set Buffer functions. While the other set of buffers is written to and/or read from by the Redstone IO device which does this in sync with minecraft's normal world ticking. I would recommend updating the emitted redstone signals during TileEntity ticks and storing the received signals during the block updates that typically come along when they get changed and assign them to the sample that corresponds to the world tick in which the update happened (this ensures reliable timing that is independent of TileEntity update order).

These buffers could be just int-arrays with length sampleCount one for each input and output channel, and could be allocated alongside the call to initBulkIO (the allowed sample count should be limited to prevent programs from causing ridiculous RAM allocation). As mentioned in the function list above, whenever the Redstone IO device is done processing its buffer, the buffers get swapped out: The contents of the program's output buffers get copied over into the device's output buffers and the other way around for the input buffers. After the buffers are successfully swapped the computer gets send the buffer_swap-event signal.

_I might do a pull request for this you want._

Side Note

This feature would allow continuous tick-precise redstone IO but it would not support immediate response of the computer to received signals. However the response delay would be precisely predictable as dt=interval*sampleCount*2. Using smaller sample counts would speed up response time but on the other hand increase the risk of the program missing its time window to deliver the next chunk of signal data (for example when other computers might block the lua threads for too long). It would be up to the player to find the best compromise for his desired goal.

Heads up -- I've built a solution for improved redstone control, I've tested it in 1.7.10, and I am currently working on merging it up to 1.12

This work will close this ticket, but may not be what everyone was expecting

I'll also do the work to describe the api in our docs, but here is a teaser

This change provides overrides for set* and get* redstone methods to allow you to optionally specify sides and colors. The less you specify, the more data you get or can set

The following are equivalent

component.redstone.setOutput( sides.right, 15 )
component.redstone.setOutput( { [sides.right] = 15 } )
component.redstone.setBundledOutput( sides.left, colors.blue, 100 )
component.redstone.setBundledOutput( sides.left,  { [colors.blue] = 100 } )
component.redstone.setBundledOutput( { [sides.left] = { [colors.blue] = 100 } } )
component.redstone.getBundledOutput( sides.left ) -- returns ALL left side data
component.redstone.getBundledOutput() -- returns everything current set for output
component.redstone.getBundledInput()

This allows setting multiple values in a single call, with the same call cost as any other set call (and not the combined set cost of doing these individually)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

wylderhoads1809 picture wylderhoads1809  路  9Comments

BloodyRain2k picture BloodyRain2k  路  6Comments

Adam-Kay picture Adam-Kay  路  5Comments

bbgott picture bbgott  路  4Comments

invliD picture invliD  路  3Comments