From ba2fc5eb73ace412f825e4a2b179666c5687bcba Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Wed, 13 May 2020 16:20:50 -0400 Subject: [PATCH] Signal management --- src/chats.nim | 1 - src/nim_status_client.nim | 23 +++++++++++-- src/nim_status_client.nim.cfg | 2 +- src/state.nim | 65 ++++++++++++++++++++++++++++++----- src/status/core.nim | 5 +-- src/status/signals.nim | 26 +++++++------- src/status/types.nim | 9 +++++ 7 files changed, 103 insertions(+), 28 deletions(-) diff --git a/src/chats.nim b/src/chats.nim index 0f2b2030c7..aad70314f4 100644 --- a/src/chats.nim +++ b/src/chats.nim @@ -28,7 +28,6 @@ QtObject: self.names.add(chatId) self.endInsertRows() - method rowCount(self: ChatsModel, index: QModelIndex = nil): int = return self.names.len diff --git a/src/nim_status_client.nim b/src/nim_status_client.nim index 9aedfe1526..beb0f5e517 100644 --- a/src/nim_status_client.nim +++ b/src/nim_status_client.nim @@ -8,6 +8,7 @@ import status/utils import status/core as status import status/chat as status_chat import status/test as status_test +import status/types as types proc mainProc() = # From QT docs: @@ -18,13 +19,16 @@ proc mainProc() = var app = newQApplication() defer: app.delete() # Defer will run this just before mainProc() function ends + var appState = state.newAppState() + echo appState.title + var chatsModel = newChatsModel(); defer: chatsModel.delete var engine = newQQmlApplicationEngine() defer: engine.delete() - status.init() + status.init(appState) status_test.setupNewAccount() discard status_test.addPeer("enode://2c8de3cbb27a3d30cbb5b3e003bc722b126f5aef82e2052aaef032ca94e0c7ad219e533ba88c70585ebd802de206693255335b100307645ab5170e88620d2a81@47.244.221.14:443") @@ -44,8 +48,7 @@ proc mainProc() = let chatsVariant = newQVariant(chatsModel) defer: chatsVariant.delete - var appState = state.newAppState() - echo appState.title + appState.subscribe(proc () = chatsModel.names = @[] @@ -55,6 +58,7 @@ proc mainProc() = ) status.startMessenger() + appState.addChannel("test") appState.addChannel("test2") @@ -62,11 +66,24 @@ proc mainProc() = engine.setRootContextProperty("chatsModel", chatsVariant) engine.load("../ui/main.qml") + + # EXAMPLE: this will be triggered once a message is received + appState.onSignal(SignalType.Message, proc(myMessage: string): void = + echo "I received a message: ", myMessage + ); + # Handle signals as part of the state + var signalWorker: Thread[AppState] + signalWorker.createThread(proc(s:AppState) = s.processSignals, appState) + defer: signalWorker.joinThread() + + # Qt main event loop is entered here # The termination of the loop will be performed when exit() or quit() is called app.exec() + + when isMainModule: mainProc() GC_fullcollect() diff --git a/src/nim_status_client.nim.cfg b/src/nim_status_client.nim.cfg index 6a6fa07699..4024805b9a 100644 --- a/src/nim_status_client.nim.cfg +++ b/src/nim_status_client.nim.cfg @@ -1,2 +1,2 @@ --threads:on ---tlsEmulation:off +--tlsEmulation:off \ No newline at end of file diff --git a/src/state.nim b/src/state.nim index 5ecbc5550b..89da1a682c 100644 --- a/src/state.nim +++ b/src/state.nim @@ -1,16 +1,33 @@ +import status/types +import tables -type Channel = object +type + ChatChannel = object name*: string -type - Subscriber* = proc () + + Subscriber* = proc () + + SignalSubscriber* = proc(p0: string) + + Signal = object + signalType*: SignalType + content*: string + +var signalChannel: Channel[Signal] type AppState* = ref object - title*: string - channels*: seq[Channel] - subscribers*: seq[Subscriber] + title*: string + channels*: seq[ChatChannel] + subscribers*: seq[Subscriber] + signalSubscribers*: Table[SignalType, seq[SignalSubscriber]] + proc newAppState*(): AppState = - result = AppState(title: "hello") + result = AppState( + title: "hello", + signalSubscribers: initTable[SignalType, seq[SignalSubscriber]]() + ) + signalChannel.open() proc subscribe*(self: AppState, subscriber: Subscriber) = self.subscribers.add(subscriber) @@ -20,5 +37,37 @@ proc dispatch*(self: AppState) = subscriber() proc addChannel*(self: AppState, name: string) = - self.channels.add(Channel(name: name)) + self.channels.add(ChatChannel(name: name)) self.dispatch() + +##################### +# Signal Handling + +proc processSignals*(self: AppState) = + ## Polls the signal channel and push the message to each subscriber + {.gcsafe.}: + while(true): + let tried = signalChannel.tryRecv() + if tried.dataAvailable and self.signalSubscribers.hasKey(tried.msg.signalType): + for subscriber in self.signalSubscribers[tried.msg.signalType]: + subscriber(tried.msg.content) + defer: + signalChannel.close() + +proc addToChannel(s: Signal) {.thread.} = + signalChannel.send(s) + +proc nextSignal*(self: AppState, signalType: SignalType, jsonMessage: string) = + ## This is called by the signal handler for each signal received and + ## adds it to the signal channel for being consumed by the SignalSubscribers + let signal: Signal = Signal(signalType: signalType, content: jsonMessage) + var worker: Thread[Signal] + createThread(worker, addToChannel, signal) + worker.joinThread() + +proc onSignal*(self: AppState, signalType: SignalType, subscriber: SignalSubscriber) = + ## Register a callback that will be executed once + ## a signal is received from status-go + if not self.signalSubscribers.hasKey(signalType): + self.signalSubscribers[signalType] = @[] + self.signalSubscribers[signalType].add(subscriber) \ No newline at end of file diff --git a/src/status/core.nim b/src/status/core.nim index a0708db3c9..c4953ba669 100644 --- a/src/status/core.nim +++ b/src/status/core.nim @@ -2,12 +2,13 @@ import libstatus import signals import types import chat +import "../state" proc setSignalHandler(signalHandler: SignalCallback) = libstatus.setSignalEventCallback(signalHandler) -proc init*() = - setSignalHandler(onSignal) +proc init*(state: AppState) = + setSignalHandler(onSignal(state)) proc startMessenger*() = chat.startMessenger() diff --git a/src/status/signals.nim b/src/status/signals.nim index 7f67884944..ade76d3d93 100644 --- a/src/status/signals.nim +++ b/src/status/signals.nim @@ -1,19 +1,19 @@ import types import json +import "../state" as state -var onSignal*: SignalCallback = proc(p0: cstring): void = - setupForeignThreadGc() - # TODO: Dispatch depending on message type $jsonSignal["type"].getStr - # Consider also have an intermediate object with an enum for type - # So you do not have to deal with json objects but with a nim type +proc onSignal*(state: AppState): SignalCallback = + result = proc(p0: cstring): void = + setupForeignThreadGc() + let jsonSignal = ($p0).parseJson + let signalType = $jsonSignal["type"].getStr - let jsonSignal = ($p0).parseJson - let messageType = $jsonSignal["type"].getStr + case signalType: + of "messages.new": + state.nextSignal(SignalType.Message, $jsonSignal) + else: + state.nextSignal(SignalType.Unknown, $jsonSignal) - case messageType: - of "messages.new": - echo $p0 - else: - discard messageType #TODO: do something - tearDownForeignThreadGc() + tearDownForeignThreadGc() + diff --git a/src/status/types.nim b/src/status/types.nim index d1a14eccea..ecd89273f8 100644 --- a/src/status/types.nim +++ b/src/status/types.nim @@ -1 +1,10 @@ +import hashes + type SignalCallback* = proc(eventMessage: cstring): void + +type SignalType* {.pure.} = enum + Message = "messages.new" + Wallet = "wallet" + NodeStarted = "node.started" + Unknown + #TODO: add missing types