package kernel; import kernel.turtle.TurtleMutex; import kernel.turtle.Turtle; import kernel.peripherals.Peripherals.Peripheral; import kernel.log.Log; import lib.Pos; import cc.HTTP.HTTPResponse; import lua.TableTools; import lua.Coroutine; import haxe.Exception; using tink.CoreApi; using lua.Table; /** Class for interacting with the native pullEvent system. **/ class KernelEvents { /** Depends on: (Nothing) **/ public static var onAlarm(default, null):Signal; public static var onChar(default, null):Signal; public static var onDisk(default, null):Signal; public static var onDiskEject(default, null):Signal; public static var onHttpCheck(default, null):Signal<{url:String, success:Bool, failReason:Any}>; public static var onHttpFailure(default, null):Signal<{url:String, failReason:String, handle:HTTPResponse}>; public static var onHttpSuccess(default, null):Signal<{url:String, handle:HTTPResponse}>; public static var onKey(default, null):Signal<{keyCode:Int, isHeld:Bool}>; public static var onKeyUp(default, null):Signal; public static var onModemMessage(default, null):Signal<{ addr:String, channel:Int, replyChannel:Int, message:Dynamic, distance:Null }>; public static var onMonitorResize(default, null):Signal; public static var onMonitorTouch(default, null):Signal<{addr:String, pos:Pos}>; public static var onMouseClick(default, null):Signal<{button:ButtonType, pos:Pos}>; public static var onMouseDrag(default, null):Signal<{button:ButtonType, pos:Pos}>; public static var onMouseScroll(default, null):Signal<{dir:Int, pos:Pos}>; public static var onMouseUp(default, null):Signal<{button:ButtonType, pos:Pos}>; public static var onPaste(default, null):Signal; public static var onPeripheral(default, null):Signal; public static var onPeripheralDetach(default, null):Signal; public static var onRedstone(default, null):Signal; public static var onSpeakerAudioEmpty(default, null):Signal; public static var onTaskComplete(default, null):Signal<{id:Int, success:Bool, failedReason:String}>; public static var onTermResize(default, null):Signal; public static var onTerminate(default, null):Signal; public static var onTimer(default, null):Signal; public static var onTurtleInventory(default, null):Signal; public static var onWebsocketClose(default, null):Signal; public static var onWebsocketFailure(default, null):Signal<{url:String, failReason:String}>; public static var onWebsocketMessage(default, null):Signal<{url:String, message:String, isBinary:Bool}>; public static var onWebsocketSuccess(default, null):Signal<{url:String, handle:Any}>; private static final onAlarmTrigger:SignalTrigger = Signal.trigger(); private static final onCharTrigger:SignalTrigger = Signal.trigger(); private static final onDiskTrigger:SignalTrigger = Signal.trigger(); private static final onDiskEjectTrigger:SignalTrigger = Signal.trigger(); private static final onHttpCheckTrigger:SignalTrigger<{url:String, success:Bool, failReason:Any}> = Signal.trigger(); private static final onHttpFailureTrigger:SignalTrigger<{url:String, failReason:String, handle:HTTPResponse}> = Signal.trigger(); private static final onHttpSuccessTrigger:SignalTrigger<{url:String, handle:HTTPResponse}> = Signal.trigger(); private static final onKeyTrigger:SignalTrigger<{keyCode:Int, isHeld:Bool}> = Signal.trigger(); private static final onKeyUpTrigger:SignalTrigger = Signal.trigger(); private static final onModemMessageTrigger:SignalTrigger<{ addr:String, channel:Int, replyChannel:Int, message:Dynamic, distance:Null }> = Signal.trigger(); private static final onMonitorResizeTrigger:SignalTrigger = Signal.trigger(); private static final onMonitorTouchTrigger:SignalTrigger<{addr:String, pos:Pos}> = Signal.trigger(); private static final onMouseClickTrigger:SignalTrigger<{button:ButtonType, pos:Pos}> = Signal.trigger(); private static final onMouseDragTrigger:SignalTrigger<{button:ButtonType, pos:Pos}> = Signal.trigger(); private static final onMouseScrollTrigger:SignalTrigger<{dir:Int, pos:Pos}> = Signal.trigger(); private static final onMouseUpTrigger:SignalTrigger<{button:ButtonType, pos:Pos}> = Signal.trigger(); private static final onPasteTrigger:SignalTrigger = Signal.trigger(); private static final onPeripheralTrigger:SignalTrigger = Signal.trigger(); private static final onPeripheralDetachTrigger:SignalTrigger = Signal.trigger(); private static final onRednetMessageTrigger:SignalTrigger<{sender:Int, message:Any, protocol:Any}> = Signal.trigger(); private static final onRedstoneTrigger:SignalTrigger = Signal.trigger(); private static final onSpeakerAudioEmptyTrigger:SignalTrigger = Signal.trigger(); private static final onTaskCompleteTrigger:SignalTrigger<{id:Int, success:Bool, failedReason:String}> = Signal.trigger(); private static final onTermResizeTrigger:SignalTrigger = Signal.trigger(); private static final onTerminateTrigger:SignalTrigger = Signal.trigger(); private static final onTimerTrigger:SignalTrigger = Signal.trigger(); private static final onTurtleInventoryTrigger:SignalTrigger = Signal.trigger(); private static final onWebsocketCloseTrigger:SignalTrigger = Signal.trigger(); private static final onWebsocketFailureTrigger:SignalTrigger<{url:String, failReason:String}> = Signal.trigger(); private static final onWebsocketMessageTrigger:SignalTrigger<{url:String, message:String, isBinary:Bool}> = Signal.trigger(); private static final onWebsocketSuccessTrigger:SignalTrigger<{url:String, handle:Any}> = Signal.trigger(); private static var stopLoop:Bool = false; private static var turtleCoroutine:Coroutine; @:allow(kernel.Init) private static function init() { onAlarm = onAlarmTrigger.asSignal(); onChar = onCharTrigger.asSignal(); onDisk = onDiskTrigger.asSignal(); onDiskEject = onDiskEjectTrigger.asSignal(); onHttpCheck = onHttpCheckTrigger.asSignal(); onHttpFailure = onHttpFailureTrigger.asSignal(); onHttpSuccess = onHttpSuccessTrigger.asSignal(); onKey = onKeyTrigger.asSignal(); onKeyUp = onKeyUpTrigger.asSignal(); onModemMessage = onModemMessageTrigger.asSignal(); onMonitorResize = onMonitorResizeTrigger.asSignal(); onMonitorTouch = onMonitorTouchTrigger.asSignal(); onMouseClick = onMouseClickTrigger.asSignal(); onMouseDrag = onMouseDragTrigger.asSignal(); onMouseScroll = onMouseScrollTrigger.asSignal(); onMouseUp = onMouseUpTrigger.asSignal(); onPaste = onPasteTrigger.asSignal(); onPeripheral = onPeripheralTrigger.asSignal(); onPeripheralDetach = onPeripheralDetachTrigger.asSignal(); onRedstone = onRedstoneTrigger.asSignal(); onSpeakerAudioEmpty = onSpeakerAudioEmptyTrigger.asSignal(); onTaskComplete = onTaskCompleteTrigger.asSignal(); onTermResize = onTermResizeTrigger.asSignal(); onTerminate = onTerminateTrigger.asSignal(); onTimer = onTimerTrigger.asSignal(); onTurtleInventory = onTurtleInventoryTrigger.asSignal(); onWebsocketClose = onWebsocketCloseTrigger.asSignal(); onWebsocketFailure = onWebsocketFailureTrigger.asSignal(); onWebsocketMessage = onWebsocketMessageTrigger.asSignal(); onWebsocketSuccess = onWebsocketSuccessTrigger.asSignal(); } /** Start pulling events. Blocking. **/ @:allow(kernel.Entrypoint) private static function startEventLoop() { if (Turtle.isTurtle()) { turtleLoop(); } else { runMainLoop(); } } @:allow(kernel.turtle.TurtleMutex) private static function startTurtleCoroutine() { turtleCoroutine = Coroutine.create(runTurtleLoop); Coroutine.resume(turtleCoroutine); } private static function turtleLoop() { startTurtleCoroutine(); while (!stopLoop) { var eventData = pullEvents(); if (eventData[1] == "turtle_response" || eventData[1] == "tthread") { var result = Coroutine.resume(turtleCoroutine, TableTools.unpack(eventData)); if (!result.success) { Log.error('Error while running turtle thread: ${result.result}'); } if (Coroutine.status(turtleCoroutine) == Dead) { Log.error('Turtle thread died'); } } else { fireSignal(eventData[1], eventData); } } } private static function runTurtleLoop() { while (!stopLoop) { if ((TableTools.pack(Coroutine.yield()))[1] != "tthread") continue; if (stopLoop) continue; TurtleMutex.runThreadFunc(); } } private static function runMainLoop() { while (!stopLoop) { var event:Table = pullEvents(); var eventName:String = event[1]; try { fireSignal(eventName, event); } catch (e:Dynamic) { Log.error('Error while handling event: $eventName: ${e}'); } } } public static function shutdown() { // clearing screens for (screen in Peripheral.getAllScreens()) { screen.reset(); } Log.info('Shutting down event loop'); stopLoop = true; MainTerm.instance.reset(); } private static function pullEvents():Table { return cast TableTools.pack(Coroutine.yield(null)); } private static function fireSignal(eventName:String, event:Table) { switch eventName { case "alarm": onAlarmTrigger.trigger(event[2]); case "char": onCharTrigger.trigger(event[2]); case "disk": onDiskTrigger.trigger(event[2]); case "disk_eject": onDiskEjectTrigger.trigger(event[2]); case "http_check": onHttpCheckTrigger.trigger({url: event[2], success: event[3], failReason: event[4]}); case "http_failure": onHttpFailureTrigger.trigger({url: event[2], failReason: event[3], handle: event[4]}); case "http_success": onHttpSuccessTrigger.trigger({url: event[2], handle: event[3]}); case "key": onKeyTrigger.trigger({keyCode: event[2], isHeld: event[3]}); case "key_up": onKeyUpTrigger.trigger(event[2]); case "modem_message": onModemMessageTrigger.trigger({ addr: event[2], channel: event[3], replyChannel: event[4], message: event[5], distance: event[6] }); case "monitor_resize": onMonitorResizeTrigger.trigger(event[2]); case "monitor_touch": onMonitorTouchTrigger.trigger({addr: event[2], pos: {x: (event[3] : Int) - 1, y: (event[4] : Int) - 1}}); case "mouse_click": onMouseClickTrigger.trigger({button: ccButtonToEnum(event[2]), pos: {x: (event[3] : Int) - 1, y: (event[4] : Int) - 1}}); case "mouse_drag": onMouseDragTrigger.trigger({button: ccButtonToEnum(event[2]), pos: {x: (event[3] : Int) - 1, y: (event[4] : Int) - 1}}); case "mouse_scroll": onMouseScrollTrigger.trigger({dir: event[2], pos: {x: (event[3] : Int) - 1, y: (event[4] : Int) - 1}}); case "mouse_up": onMouseUpTrigger.trigger({button: ccButtonToEnum(event[2]), pos: {x: (event[3] : Int) - 1, y: (event[4] : Int) - 1}}); case "paste": onPasteTrigger.trigger(event[2]); case "peripheral": onPeripheralTrigger.trigger(event[2]); case "peripheral_detach": onPeripheralDetachTrigger.trigger(event[2]); case "redstone": onRedstoneTrigger.trigger(null); case "speaker_audio_empty": onSpeakerAudioEmptyTrigger.trigger(event[2]); case "task_complete": onTaskCompleteTrigger.trigger({id: event[2], success: event[3], failedReason: event[4]}); case "term_resize": onTermResizeTrigger.trigger(null); case "terminate": onTerminateTrigger.trigger(null); case "timer": onTimerTrigger.trigger(event[2]); case "turtle_inventory": onTurtleInventoryTrigger.trigger(null); case "websocket_closed": onWebsocketCloseTrigger.trigger(event[2]); case "websocket_failure": onWebsocketFailureTrigger.trigger({url: event[2], failReason: event[3]}); case "websocket_message": onWebsocketMessageTrigger.trigger({url: event[2], message: event[3], isBinary: event[4]}); case "websocket_success": onWebsocketSuccessTrigger.trigger({url: event[2], handle: event[3]}); case "endofloop": EndOfLoop.run(); default: Log.error('Unknown event: $eventName'); } } private static function ccButtonToEnum(button:Dynamic):ButtonType { switch button { case 1: return Left; case 2: return Middle; case 3: return Right; default: throw new Exception("Invalid input"); } } @:allow(lib.Debug) private static function printListenerCount() { if (onAlarmTrigger.getLength() > 0) Log.debug("onAlarm: " + onAlarmTrigger.getLength()); if (onCharTrigger.getLength() > 0) Log.debug("onChar: " + onCharTrigger.getLength()); if (onDiskTrigger.getLength() > 0) Log.debug("onDisk: " + onDiskTrigger.getLength()); if (onDiskEjectTrigger.getLength() > 0) Log.debug("onDiskEject: " + onDiskEjectTrigger.getLength()); if (onHttpCheckTrigger.getLength() > 0) Log.debug("onHttpCheck: " + onHttpCheckTrigger.getLength()); if (onHttpFailureTrigger.getLength() > 0) Log.debug("onHttpFailure: " + onHttpFailureTrigger.getLength()); if (onHttpSuccessTrigger.getLength() > 0) Log.debug("onHttpSuccess: " + onHttpSuccessTrigger.getLength()); if (onKeyTrigger.getLength() > 0) Log.debug("onKey: " + onKeyTrigger.getLength()); if (onKeyUpTrigger.getLength() > 0) Log.debug("onKeyUp: " + onKeyUpTrigger.getLength()); if (onModemMessageTrigger.getLength() > 0) Log.debug("onModemMessage: " + onModemMessageTrigger.getLength()); if (onMonitorResizeTrigger.getLength() > 0) Log.debug("onMonitorResize: " + onMonitorResizeTrigger.getLength()); if (onMonitorTouchTrigger.getLength() > 0) Log.debug("onMonitorTouch: " + onMonitorTouchTrigger.getLength()); if (onMouseClickTrigger.getLength() > 0) Log.debug("onMouseClick: " + onMouseClickTrigger.getLength()); if (onMouseDragTrigger.getLength() > 0) Log.debug("onMouseDrag: " + onMouseDragTrigger.getLength()); if (onMouseScrollTrigger.getLength() > 0) Log.debug("onMouseScroll: " + onMouseScrollTrigger.getLength()); if (onMouseUpTrigger.getLength() > 0) Log.debug("onMouseUp: " + onMouseUpTrigger.getLength()); if (onPasteTrigger.getLength() > 0) Log.debug("onPaste: " + onPasteTrigger.getLength()); if (onPeripheralTrigger.getLength() > 0) Log.debug("onPeripheral: " + onPeripheralTrigger.getLength()); if (onPeripheralDetachTrigger.getLength() > 0) Log.debug("onPeripheralDetach: " + onPeripheralDetachTrigger.getLength()); if (onRedstoneTrigger.getLength() > 0) Log.debug("onRedstone: " + onRedstoneTrigger.getLength()); if (onSpeakerAudioEmptyTrigger.getLength() > 0) Log.debug("onSpeakerAudioEmpty: " + onSpeakerAudioEmptyTrigger.getLength()); if (onTaskCompleteTrigger.getLength() > 0) Log.debug("onTaskComplete: " + onTaskCompleteTrigger.getLength()); if (onTermResizeTrigger.getLength() > 0) Log.debug("onTermResize: " + onTermResizeTrigger.getLength()); if (onTerminateTrigger.getLength() > 0) Log.debug("onTerminate: " + onTerminateTrigger.getLength()); if (onTimerTrigger.getLength() > 0) Log.debug("onTimer: " + onTimerTrigger.getLength()); if (onTurtleInventoryTrigger.getLength() > 0) Log.debug("onTurtleInventory: " + onTurtleInventoryTrigger.getLength()); if (onWebsocketCloseTrigger.getLength() > 0) Log.debug("onWebsocketClose: " + onWebsocketCloseTrigger.getLength()); if (onWebsocketFailureTrigger.getLength() > 0) Log.debug("onWebsocketFailure: " + onWebsocketFailureTrigger.getLength()); if (onWebsocketMessageTrigger.getLength() > 0) Log.debug("onWebsocketMessage: " + onWebsocketMessageTrigger.getLength()); if (onWebsocketSuccessTrigger.getLength() > 0) Log.debug("onWebsocketSuccess: " + onWebsocketSuccessTrigger.getLength()); } }