OpenMods / OpenPeripheral-Addons

An addon for OpenPeripheral, adding physical blocks and items to the game
MIT License
11 stars 10 forks source link

sensor.getPlayers() skips events #70

Closed applejag closed 9 years ago

applejag commented 9 years ago

I've noticed while testing that the sensor.getPlayers() method messes up my scripts.

Simple example

local s = peripheral.wrap("back")

local delay = 0.1
local timer = os.startTimer(delay)
local running = true

while running do
    -- simple event system
    local ev,p1 = os.pullEvent()

    -- main clock
    if ev == "timer" and p1 == timer then
        timer = os.startTimer(delay)

        -- testing text to see that it's running
        term.setCursorPos(1,1)
        term.clearLine()
        term.write(textutils.formatTime(os.time(),true))

        -- the glitchy part
        s.getPlayers()
    end

    -- reading input
    if ev == "key" and p1 == keys.q then
        running = false
    end
end

print()

For some reason the sensor.getPlayers() function slows down the script so it actually misses events and even sometimes skips timer events somehow.

Idk the reason behind this but it misses about every other event except for the timer events

boq commented 9 years ago

It's expected behaviour

getPlayers is synchronized: it's called in world thread, while Lua code is executed in dedicated ComputerCraft thread.

Call to this method is actually equivalent to this:

CC thread:
schedule task for world thread
task_id = id of scheduled task
while true:
    evt,id = os.pullEvent()
    if evt == 'task_complete' and id = task_id: break

World thread:
do stuff
yield 'task_complete',task_id

This is simplification (we use ILuaContext.issueMainThreadTask from CC API), but yes, it's dropping events by default.

If you need to be sure you get everything, use parallel API.

applejag commented 9 years ago

Could you not move it to a CC thread instead? I mean the sensors functions are kinda ment to be spammed

boq commented 9 years ago

Without synchronization it VERY easy to crash game. Moving this to CC thread is impossible.

Also, please note that "one per tick" is enough - because nothing tracked by sensor can change outside of tick.

applejag commented 9 years ago

Well ok, guess I'll have to look into coroutines?

And the minimum time you can wait with a timer event is one tick so that should be no problem.

nevercast commented 9 years ago

Cache the results in code and expire the sensor data once a tick. It's called memoization and in this case you should be doing it.

Coroutines won't help much. They are not threads they are execution contexts and only one runs at once.

applejag commented 9 years ago

Got a while loop in a parallel.waitForAll() and it works, don't know if it's a good solution or not but it works. Got a sleep in there too so dont worry about doing it too often

The parallel api makes a duplicate of every event which is why i think this works..

boq commented 9 years ago

Yup, parallel.waitForAll() is correct approach.

@nevercast Actually, knowledge of coroutines is helpful for understanding parallel module, since signals and resuming are done with them. This is snippet from CC bios.lua;

function os.pullEventRaw( sFilter )
    return coroutine.yield( sFilter )
end

parallel.waitForAll()? Just start one coroutine for every function, call os.pullEvent in loop and resume every sub-coroutine with result of it.

nevercast commented 9 years ago

Yes correct both parallel functions pass the events to all the children coroutines. Though it was my belief that they weren't needed in this case.

Also sleeping yields. It will eat events. Be aware of that also.

applejag commented 9 years ago

I know how it Works dw