Skip to content

Commit

Permalink
Reworking Events
Browse files Browse the repository at this point in the history
  • Loading branch information
stricklandrbls committed Aug 19, 2024
1 parent fea2736 commit 5585137
Show file tree
Hide file tree
Showing 10 changed files with 455 additions and 9 deletions.
28 changes: 27 additions & 1 deletion src/dataEditor/dataEditorClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,13 @@ import {
} from './include/server/ServerInfo'
import { isDFDLDebugSessionActive } from './include/utils'
import { SvelteWebviewInitializer } from './svelteWebviewInitializer'
import {
DataEditorEventManager,
DataEditorMessenger,
DataEditorResponder,
} from './messages'
import { DefaultInputListeners } from './eventResponder'
import { EventEmitter } from 'stream'

// *****************************************************************************
// global constants
Expand Down Expand Up @@ -138,7 +145,13 @@ export function activate(ctx: vscode.ExtensionContext): void {
// *****************************************************************************

export class DataEditorClient implements vscode.Disposable {
// private messenger = new DataEditorMessenger((type, msg) => {
// this.panel.webview.postMessage({ ...msg })
// this.panel.webview.html.
// })

public panel: vscode.WebviewPanel
private eventResponder: DataEditorResponder
private svelteWebviewInitializer: SvelteWebviewInitializer
private displayState: DisplayState
private currentViewportId: string
Expand All @@ -154,6 +167,12 @@ export class DataEditorClient implements vscode.Disposable {
private configVars: editor_config.IConfig,
fileToEdit: string = ''
) {
// let intCount = 0
// const id = setInterval(() => {
// console.log("Sending test 'saveSegment'")
// this.messenger.send('saveSegment', { length: 69, startOffset: 420 })
// if (++intCount > 10) clearInterval(id)
// }, 5000)
const column =
fileToEdit !== '' ? vscode.ViewColumn.Two : vscode.ViewColumn.Active
this.panel = vscode.window.createWebviewPanel(this.view, title, column, {
Expand Down Expand Up @@ -182,7 +201,14 @@ export class DataEditorClient implements vscode.Disposable {
)
context.subscriptions.push(this)

this.svelteWebviewInitializer = new SvelteWebviewInitializer(context)
const { responder, requester } = DataEditorEventManager.EventChannel(
this.omegaSessionId
)

this.svelteWebviewInitializer = new SvelteWebviewInitializer(
context,
this.omegaSessionId
)
this.svelteWebviewInitializer.initialize(this.view, this.panel.webview)
this.currentViewportId = ''
this.fileToEdit = fileToEdit
Expand Down
14 changes: 14 additions & 0 deletions src/dataEditor/eventResponder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { DataEditorResponseEvents } from './messages'

type DefaultEventResponses = {
[R in keyof DataEditorResponseEvents]: (
request: DataEditorResponseEvents[R]
) => void
}
export const DefaultInputListeners: DefaultEventResponses = {
requestEditedData: function (
request: [response: { bytes: Uint8Array; str: string }]
): void {
throw new Error('Function not implemented.')
},
}
9 changes: 9 additions & 0 deletions src/dataEditor/messages/dataEditorMessages.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ export type RequestEditedData = {
radix: DisplayRadix
editMode: EditByteModes
}

// export type RequestEditedData = [
// viewportId: string,
// startOffset: number,
// selectionSize: number,
// editedContentStr: string,
// radix: DisplayRadix,
// editMode: EditByteModes,
// ]
export type ScrollViewport = {
scrollToOffset: number
bytesPerRow: number
Expand Down
95 changes: 95 additions & 0 deletions src/dataEditor/messages/eventChannel.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import assert from 'assert'
import { describe } from 'mocha'
import { EventEmitter } from 'stream'

interface RequestEmitter<RequestTypes> {
on<R extends keyof RequestTypes>(
event: R,
listener: (...args: ) => any
): any
}
interface ResponseEmitter<ResponseTypes> {
on(event: string, listener: (...args: any[]) => any): any
}

/// The extension handles all data anyways so it should define both request and response types, or the requester could send any arbitrary request event
class EventChannel<RequestTypes, ResponseTypes> {
__req: RequestEmitter<RequestTypes> | undefined = undefined
__res: ResponseEmitter<ResponseTypes> | undefined = undefined
constructor() {}
setRequester(req: RequestEmitter<RequestTypes>) {
if (this.__req) throw 'A Requester already exists on this channel'
this.__req = req
}
setResponder(res: ResponseEmitter<ResponseTypes>) {
if (this.__res) throw 'A Responder already exists on this channel'
this.__res = res
}

}

const EventChannelMap: Map<string, EventChannel<any, any>> = new Map()
class EventManager {
static CreateChannel<Req, Res>(id: string): EventChannel<Req, Res> {
const channel = new EventChannel<Req, Res>()
EventChannelMap.set(id, channel)
return channel
}
static GetChannel<ID extends string>(id: string): EventChannel<EventChannelMap[ID],Res> {
if (!EventChannelMap.get(id)) throw `No Channel with ID = ${id}`
return EventChannelMap.get(id)!
}
}

interface UIEvents {
e1: []
save: [data: { filename: string }]
shutdown: [data: { filename: string; time: number }]
}
class UIEventEmitter implements RequestEmitter<UIEvents> {
constructor(private __emitter: EventEmitter<UIEvents>) {}
// __emitter = new EventEmitter<UIEvents>()
on = this.__emitter.on
}
class UI {
readonly __docID = 'session-id'
__emitter = new EventEmitter<UIEvents>()
requester: RequestEmitter<UIEvents> = new UIEventEmitter(this.__emitter)
constructor() {
EventManager.GetChannel(this.__docID).__req = this.requester
}
simulateResponseListenerAdd() {}
simulateInputReceived() {
this.__emitter.emit('shutdown', { filename: 'testfile/name.bin', time: 0 })
}
}
interface ExtResponses {
'data-refresh': [data: { encoding: string; offset: number; data: Uint8Array }]
'file-info': [data: { filename: string; filesize: number }]
}
class ExtResponder implements ResponseEmitter<ExtResponses> {
__emitter = new EventEmitter<ExtResponses>()
on = this.__emitter.on
}
class Ext {
readonly __sessionId = 'session-id'
responder: ResponseEmitter<ExtResponses> = new ExtResponder()
constructor() {
const channel = EventManager.CreateChannel<UIEvents, ExtResponses>(this.__sessionId)
}
}
const ui = new UI()
describe('Event Channel Behavior', () => {
describe('Manager', () => {
it('Should create a channel if one does not already exist', () => {
const channel = EventManager.GetChannel(ui.__docID)
assert.equal(EventChannelMap.size, 1)
assert(EventChannelMap.get(ui.__docID))
assert(channel)
EventChannelMap.clear()
})
})
it('Should allow a requester to add listeners to events that the responder emits', () => {})
it('Should allow a responder to add listeners to events that the requester emits', () => {})
it('Should contain requester and responder abstractions', () => {})
})
103 changes: 100 additions & 3 deletions src/dataEditor/messages/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as msg_types from './dataEditorMessages'
import { EventEmitter } from 'stream'
import type * as msg_types from './dataEditorMessages.d.ts'

export interface DataEditorMessage {
clearChanges: undefined
Expand All @@ -15,10 +16,28 @@ export interface DataEditorMessage {
search: msg_types.Search
replace: msg_types.Replace
}
export interface DataEditorInputMessages {}
export interface DataEditorResponseMessages {}
/// Events that the extension should handle and emit a Response Event
export interface DataEditorRequestEvents {
clearChanges: []
redoChange: []
undoChange: []
saveAs: []
save: []
applyChanges: [msg_types.ApplyChanges]
requestEditedData: [msg_types.RequestEditedData]
scrollViewport: [msg_types.ScrollViewport]
editorOnChange: [msg_types.EditorOnChange]
saveSegment: [msg_types.SaveSegment]
profile: [msg_types.Profile]
search: [msg_types.Search]
replace: [msg_types.Replace]
}
export interface DataEditorResponseEvents {
requestEditedData: [response: { bytes: Uint8Array; str: string }]
}

export class DataEditorMessenger {
private emitter = new EventEmitter<DataEditorRequestEvents>()
constructor(private sendMessage: MessengerStrategy) {}
send<T extends keyof DataEditorMessage>(
type: T,
Expand All @@ -31,4 +50,82 @@ export class DataEditorMessenger {
}
}

export class UIRequestSender {
private emitter = new EventEmitter<DataEditorRequestEvents>()
constructor() {
// this.getEmitter('applyChanges')()
}
getEmitter<T extends keyof DataEditorRequestEvents>(type: T) {
return this.emitter.emit<T>
}
}
export type MessengerStrategy = (type: string, msg?: object) => any
export type DataEditorRequester = EventEmitter<DataEditorRequestEvents>
export type DataEditorResponder = EventEmitter<DataEditorResponseEvents>
export abstract class DataEditorRequestEventMap<
Requests extends DataEditorMessage,
> {
constructor(
private map: {
[K in keyof Requests]: (req: Requests[K]) => void
}
) {}
abstract on<K extends keyof Requests>(
event: K,
listener: (req: Requests[K]) => void
)
}
class DataEditorEventChannel {
constructor(
readonly id: string,
private requester_: DataEditorRequester = new EventEmitter<DataEditorRequestEvents>(),
private responder_: DataEditorResponder = new EventEmitter<DataEditorResponseEvents>()
) {}
get requester() {
return this.requester_
}
get responder() {
return this.responder_
}

set requester(req: DataEditorRequester) {
if (this.requester)
throw `A requester already exists for event channel [${this.id}]`
this.requester = req
}
set responder(res: DataEditorResponder) {
if (this.responder)
throw `A responder already exists for event channel [${this.id}]`
this.responder = res
}
}
const EventChannels: Map<string, DataEditorEventChannel> = new Map()

export class DataEditorEventManager {
// responder = new EventEmitter<DataEditorResponseEvents>()
private constructor() {}
static EventChannel(id: string): DataEditorEventChannel {
if (!EventChannels.get(id))
EventChannels.set(id, new DataEditorEventChannel(id))
return EventChannels.get(id)!
}
}
export const DataEditorInputEvent = new EventEmitter<DataEditorRequestEvents>()
/*
EXT
- Handles certain messages that are received from UI.
- Should declare to UI what messages can be created.
- Derived EXT classes can define additional events (how to customize listeners in UI w/o listening to all?)
UI
- Post messages to EXT when an input event is triggered
- Handle EXT response messages.
*/
// const SvelteEventEmitter = new EventEmitter<DataEditorRequestEvents>()
// // SvelteEventEmitter.on('...') // Register event listeners for each input event
// SvelteEventEmitter.on('requestEditedData', (args) => {
// // specific event operation
// this.responder.emit('requestEditedData', {
// bytes: Uint8Array.from([0xff]),
// str: (0xff).toString(),
// })
83 changes: 83 additions & 0 deletions src/dataEditor/messages/manager.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import assert from 'assert'
import EventEmitter from 'events'
import { describe } from 'mocha'

interface RequestEvents {
ping: []
scroll: [data: { offset: number }]
multidata: [data: { str: string; bin: Uint8Array; num: number }]
}
interface ResponseEvents {
pong: []
'file-info': [data: { filename: string; size: number }]
}
type EventMap = { [K: string]: [any] }
type RequestListener<E> = {
on<Key extends string & keyof E>(
event: Key,
listener: (content: E[Key]) => void
): void
}
class RequestEmitter extends EventEmitter {}
class Requester<EventTypes> {
constructor(
public __channelNotify: <R extends keyof EventTypes>(
type: R,
data: EventTypes[R]
) => void
) {}
request<R extends keyof EventTypes>(type: R, data: EventTypes[R]) {}
}
class Responder<EventTypes> {
constructor(
public __channelNotify: <R extends keyof EventTypes>(
type: R,
data: EventTypes[R]
) => void
) {}
respond<R extends keyof EventTypes>(type: R, data: EventTypes[R]) {}
}
class ChannelMember<OutEvents, InEvents> {
constructor(
readonly send: <R extends keyof OutEvents>(
type: R,
data: OutEvents[R]
) => void,
readonly on: <R extends keyof InEvents>(
type: R,
listener: (data: InEvents[R]) => void
) => void
) {}
}
class EventChannel<Req, Res> {
__requester: ChannelMember<Req, Res> | undefined
__requestMap: { [K in keyof Res]: (request: Res[K]) => void }
__responder: ChannelMember<Res, Req> | undefined
__responseMap: { [K in keyof Req]: (request: Req[K]) => void }
constructor() {}
createRequester() {
this.__requester = new ChannelMember<Req, Res>(
(type, data) => {
this.__responseMap![type](data)
},
(type, listener) => {
this.__requestMap![type] = listener
}
)
return this.__requester
}
createResponder() {
this.__responder = new ChannelMember<Res, Req>((type, data) => {
this.__requestMap![type](data)
})
return this.__responder
}
}

// class EventChannelManager {
// static CreateChannel<Req, Res>(): EventChannel
// }
describe('', () => {
const ec = new EventChannel<RequestEvents, ResponseEvents>()
const req = ec.createRequester().send('ping', [])
})
Loading

0 comments on commit 5585137

Please sign in to comment.