##// END OF EJS Templates
working on Observable
cin -
r14:53e756f117f7 propose cancellat...
parent child
Show More
@@ -0,0 +1,21
1 import * as TraceSource from './TraceSource'
2
3 class TraceEvent {
4 readonly source: TraceSource;
5
6 readonly level: Number;
7
8 readonly arg: any;
9
10 constructor(source: TraceSource, level: Number, arg: any) {
11 this.source = source;
12 this.level = level;
13 this.arg = arg;
14 }
15 }
16
17 namespace TraceEvent {
18
19 }
20
21 export = TraceEvent No newline at end of file
@@ -0,0 +1,29
1 import { IObservable } from "../../interfaces";
2 import * as TraceEvent from '../TraceEvent';
3 import { ICancellation } from "../../interfaces";
4 import { Cancellation } from "../../Cancellation";
5 import * as TraceSource from "../TraceSource";
6
7 class ConsoleWriter {
8 async write(source: IObservable<TraceEvent>, ct: ICancellation = Cancellation.none) {
9 let next;
10 while(next = await source.next(ct)) {
11 this._writeEvent(next);
12 }
13 }
14
15 private _writeEvent(next: TraceEvent) {
16 if (next.level >= TraceSource.LogLevel) {
17 console.log(next.source.toString(), next.arg);
18 } else if(next.level >= TraceSource.WarnLevel) {
19 console.warn(next.source.toString(), next.arg);
20 } else {
21 console.error(next.source.toString(), next.arg);
22 }
23 }
24 }
25
26 namespace ConsoleWriter {
27 }
28
29 export = ConsoleWriter; No newline at end of file
@@ -1,85 +1,94
1 import { IObservable, IObserver, IDestroyable, ICancellation } from '../interfaces';
1 import { IObservable, IDestroyable, ICancellation } from '../interfaces';
2 2 import { Cancellation } from '../Cancellation'
3 import * as TraceSource from '../log/TraceSource'
4 3 import { argumentNotNull } from '../safe';
5 4
6 const trace = TraceSource.get('@implab/core/components/Observable');
7
8 5
9 class Observable<T> implements IObservable<T> {
10 private _once = new Array<IObserver<T>>();
6 interface Handler<T> {
7 (x:T) : void
8 }
11 9
12 private readonly _observers = new Array<IObserver<T>>();
10 interface Initializer<T> {
11 (notify: Handler<T>) : (() => void) | void;
12 }
13
13 14
14 constructor(func: (notify: IObserver<T>) => void) {
15 argumentNotNull(func, "func");
15 class Observable<T> implements IObservable<T>, IDestroyable {
16 private _once = new Array<Handler<T>>();
17
18 private readonly _observers = new Array<Handler<T>>();
16 19
17 func(this._notify.bind(this));
20 private readonly _cleanup : (() => void) | void;
21
22 constructor(func?: Initializer<T>) {
23 this._cleanup = func && func(this._notify.bind(this));
18 24 }
19 25
20 on(observer: IObserver<T>): IDestroyable {
26 on(observer: Handler<T>, error?: Handler<any>, complete?: () => void): IDestroyable {
21 27 argumentNotNull(observer, "observer");
22 28
23 29 this._observers.push(observer);
24 30
25 31 let me = this;
26 32 return {
27 33 destroy() {
28 34 me._removeObserver(observer);
29 35 }
30 36 }
31 37 }
32 38
33 wait(ct: ICancellation = Cancellation.none): Promise<T> {
39 next(ct: ICancellation = Cancellation.none): Promise<T> {
34 40 return new Promise<T>((resolve, reject) => {
35 41 this._once.push(resolve);
36 42 if (ct.isSupported()) {
37 43 ct.register((e) => {
38 44 this._removeOnce(resolve);
39 45 reject(e);
40 46 });
41 47 }
42 48 });
43 49 }
44 50
45 onObserverException(e: any) {
46 trace.error("Unhandled exception in the observer: {0}", e);
51 destroy() {
52 if(this._cleanup)
53 this._cleanup.call(null);
47 54 }
48 55
49 private _removeOnce(d: IObserver<T>) {
56 protected onObserverException(e: any) {
57 }
58
59 private _removeOnce(d: Handler<T>) {
50 60 let i = this._once.indexOf(d);
51 61 if (i >= 0)
52 62 this._once.splice(i);
53 63 }
54 64
55 private _removeObserver(d: IObserver<T>) {
65 private _removeObserver(d: Handler<T>) {
56 66 let i = this._observers.indexOf(d);
57 67 if (i >= 0)
58 68 this._observers.splice(i);
59 69 }
60 70
61 private _notify(evt: T) {
62 let guard = (observer: IObserver<T>) => {
71 protected _notify(evt: T) {
72 let guard = (observer: Handler<T>) => {
63 73 try {
64 74 observer(evt);
65 75 } catch (e) {
66 76 this.onObserverException(e);
67 77 }
68 78 }
69 79
70 80 if (this._once.length) {
71 81 for (let i = 0; i < this._once.length; i++)
72 82 guard(this._once[i]);
73 83 this._once = [];
74 84 }
75 85
76 86 for (let i = 0; i < this._observers.length; i++)
77 87 guard(this._observers[i]);
78 88 }
79 89 }
80 90
81 91 namespace Observable {
82 export const traceSource = trace;
83 92 }
84 93
85 94 export = Observable; No newline at end of file
@@ -1,83 +1,76
1 import { watchFile } from "fs";
2
3 1 export interface IDestroyable {
4 2 destroy();
5 3 }
6 4
7 5 export interface ICancellation {
8 6 throwIfRequested(): void;
9 7 isRequested(): boolean;
10 8 isSupported(): boolean;
11 9 register(cb: (e: any) => void): void;
12 10 }
13 11
14 12 /**
15 13 * Π˜Π½Ρ‚Π΅Ρ€Ρ„Π΅ΠΉΡ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°ΡŽΡ‰ΠΈΠΉ Π°ΡΠΈΠ½Ρ…Ρ€ΠΎΠ½Π½ΡƒΡŽ Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΡŽ
16 14 */
17 15 export interface IActivatable {
18 16 /**
19 17 * @returns Boolean indicates the current state
20 18 */
21 19 isActive(): boolean;
22 20
23 21 /**
24 22 * Starts the component activation
25 23 * @param ct cancellation token for this operation
26 24 */
27 25 activate(ct?: ICancellation) : Promise<void>;
28 26
29 27 /**
30 28 * Starts the component deactivation
31 29 * @param ct cancellation token for this operation
32 30 */
33 31 deactivate(ct?: ICancellation) : Promise<void>;
34 32
35 33 /**
36 34 * Sets the activation controller for this component
37 35 * @param controller The activation controller
38 36 *
39 37 * Activation controller checks whether this component
40 38 * can be activated and manages the active state of the
41 39 * component
42 40 */
43 41 setActivationController(controller: IActivationController);
44 42
45 43 /**
46 44 * Gets the current activation controller for this component
47 45 */
48 46 getActivationController(): IActivationController;
49 47 }
50 48
51 49 export interface IActivationController {
52 50 activating(component: IActivatable, ct?: ICancellation): Promise<void>;
53 51
54 52 activated(component: IActivatable, ct?: ICancellation): Promise<void>;
55 53
56 54 deactivating(component: IActivatable, ct?: ICancellation): Promise<void>;
57 55
58 56 deactivated(component: IActivatable, ct?: ICancellation): Promise<void>;
59 57
60 58 deactivate(ct?: ICancellation): Promise<void>;
61 59
62 60 activate(component: IActivatable, ct?: ICancellation): Promise<void>;
63 61
64 62 getActive(): IActivatable;
65 63 }
66 64
67 65 export interface IAsyncComponent {
68 66 getCompletion(): Promise<void>;
69 67 }
70 68
71 69 export interface ICancellable {
72 70 cancel(reason?: any): void;
73 71 }
74 72
75 export interface IObserver<T> {
76 (x:T): void;
77 }
78
79 73 export interface IObservable<T> {
80 on(observer: IObserver<T>): IDestroyable;
81
82 wait(ct?: ICancellation) : Promise<T>;
74 on(next: (x:T) => void, error?: (e:any) => void, complete?:() => void): IDestroyable;
75 next(ct?: ICancellation) : Promise<T>;
83 76 } No newline at end of file
@@ -1,207 +1,171
1 1 import * as format from '../text/format'
2 2 import { argumentNotNull } from '../safe';
3
4 interface TraceEventHandler {
5 (sender: TraceSource, level: number, arg: any): void;
6 }
7
8 interface TraceSourceHandler {
9 (source: TraceSource): void;
10 }
11
12 interface Destroyable {
13 destroy();
14 }
3 import * as Observable from '../components/Observable'
4 import { IDestroyable } from '../interfaces';
5 import * as TraceEvent from './TraceEvent'
15 6
16 7 class Registry {
17 8 static readonly instance = new Registry();
18 9
19 10 private _registry: object = new Object();
20 11 private _listeners: object = new Object();
21 12 private _nextCookie: number = 1;
22 13
23 14 get(id: any): TraceSource {
24 15 argumentNotNull(id, "id");
25 16
26 17 if (this._registry[id])
27 18 return this._registry[id];
28 19
29 20 var source = new TraceSource(id);
30 21 this._registry[id] = source;
31 22 this._onNewSource(source);
32 23
33 24 return source;
34 25 }
35 26
36 27 add(id: any, source: TraceSource) {
37 28 argumentNotNull(id, "id");
38 29 argumentNotNull(source, "source");
39 30
40 31 this._registry[id] = source;
41 32 this._onNewSource(source);
42 33 }
43 34
44 35 _onNewSource(source: TraceSource) {
45 36 for (let i in this._listeners)
46 37 this._listeners[i].call(null, source);
47 38 }
48 39
49 on(handler: TraceSourceHandler): Destroyable {
40 on(handler: (source: TraceSource) => void): IDestroyable {
50 41 argumentNotNull(handler, "handler");
51 42 var me = this;
52 43
53 44 var cookie = this._nextCookie++;
54 45
55 46 this._listeners[cookie] = handler;
56 47
57 48 for (let i in this._registry)
58 49 handler(this._registry[i]);
59 50
60 51 return {
61 52 destroy() {
62 53 delete me._listeners[cookie];
63 54 }
64 55 };
65 56 }
66 57 }
67 58
68 class TraceSource {
69
59 class TraceSource extends Observable<TraceEvent> {
70 60 readonly id: any
71 61
72 // using array will provide faster iteration the with object
73 private _handlers: Array<TraceEventHandler> = new Array<TraceEventHandler>();
74
75 62 level: number
76 63
77 64 constructor(id: any) {
65 super();
78 66 this.id = id || new Object();
79 67 }
80 68
81 on(handler: TraceEventHandler): Destroyable {
82 argumentNotNull(handler, "handler");
83 var me = this;
84 me._handlers.push(handler);
85
86 return {
87 destroy() {
88 me.remove(handler);
89 }
90 }
91 }
92
93 remove(handler: TraceEventHandler): void {
94 let i = this._handlers.indexOf(handler);
95 if (i >= 0)
96 this._handlers.splice(i, 1);
97 }
98
99 69 protected emit(level: number, arg: any) {
100 this._handlers.forEach(h => {
101 try {
102 h(this, level, arg);
103 } catch (e) {
104 // suppress error in log handlers
105 }
106 });
70 this._notify(new TraceEvent(this, level, arg));
107 71 }
108 72
109 73 isDebugEnabled() {
110 74 return this.level >= TraceSource.DebugLevel;
111 75 }
112 76
113 77 debug(msg: string, ...args: any[]) {
114 78 if (this.isEnabled(TraceSource.DebugLevel))
115 79 this.emit(TraceSource.DebugLevel, format(msg, args));
116 80 }
117 81
118 82 isLogEnabled() {
119 83 return this.level >= TraceSource.LogLevel;
120 84 }
121 85
122 86 log(msg: string, ...args: any[]) {
123 87 if (this.isEnabled(TraceSource.LogLevel))
124 88 this.emit(TraceSource.LogLevel, format(msg, args));
125 89 }
126 90
127 91 isWarnEnabled() {
128 92 return this.level >= TraceSource.WarnLevel;
129 93 }
130 94
131 95 warn(msg: string, ...args: any[]) {
132 96 if (this.isEnabled(TraceSource.WarnLevel))
133 97 this.emit(TraceSource.WarnLevel, format(msg, args));
134 98 }
135 99
136 100 /**
137 101 * returns true if errors will be recorded.
138 102 */
139 103 isErrorEnabled() {
140 104 return this.level >= TraceSource.ErrorLevel;
141 105 }
142 106
143 107 /**
144 108 * Traces a error.
145 109 *
146 110 * @param msg the message.
147 111 * @param args parameters which will be substituted in the message.
148 112 */
149 113 error(msg: string, ...args: any[]) {
150 114 if (this.isEnabled(TraceSource.ErrorLevel))
151 115 this.emit(TraceSource.ErrorLevel, format(msg, args));
152 116 }
153 117
154 118 /**
155 119 * Checks whether the specified level is enabled for this
156 120 * trace source.
157 121 *
158 122 * @param level the trace level which should be checked.
159 123 */
160 124 isEnabled(level: number) {
161 125 return (this.level >= level);
162 126 }
163 127
164 128 /**
165 129 * Traces a raw event, passing data as it is to the underlying listeners
166 130 *
167 131 * @param level the level of the event
168 132 * @param arg the data of the event, can be a simple string or any object.
169 133 */
170 134 traceEvent(level: number, arg: any) {
171 135 if (this.isEnabled(level))
172 136 this.emit(level, arg);
173 137 }
174 138
175 139 /**
176 140 * Register the specified handler to be called for every new and already
177 141 * created trace source.
178 142 *
179 143 * @param handler the handler which will be called for each trace source
180 144 */
181 static on(handler: TraceSourceHandler) {
145 static on(handler: (source: TraceSource) => void) {
182 146 return Registry.instance.on(handler);
183 147 }
184 148
185 149 /**
186 150 * Creates or returns already created trace source for the specified id.
187 151 *
188 152 * @param id the id for the trace source
189 153 */
190 154 static get(id: any) {
191 155 return Registry.instance.get(id);
192 156 }
193 157 }
194 158
195 159 namespace TraceSource {
196 160 export const DebugLevel = 400;
197 161
198 162 export const LogLevel = 300;
199 163
200 164 export const WarnLevel = 200;
201 165
202 166 export const ErrorLevel = 100;
203 167
204 168 export const SilentLevel = 0;
205 169 }
206 170
207 171 export = TraceSource; No newline at end of file
@@ -1,110 +1,108
1 1 import * as tape from 'tape';
2 2 import * as ActivatableMixin from '@implab/core/components/ActivatableMixin';
3 3 import { AsyncComponent } from '@implab/core/components/AsyncComponent';
4 import { IActivationController } from '@implab/core/components/IActivationController';
5 import { IActivatable } from '@implab/core/components/IActivatable';
6 import { ICancellation } from '@implab/core/ICancellation';
7 import { EmptyCancellation } from '@implab/core/EmptyCancellation';
4 import { IActivationController, IActivatable, ICancellation } from '@implab/core/interfaces';
5 import { Cancellation } from '@implab/core/Cancellation';
8 6
9 7 class SimpleActivatable extends ActivatableMixin(AsyncComponent) {
10 8
11 9 }
12 10
13 11 class MockActivationController implements IActivationController {
14 12
15 13 _active: IActivatable = null;
16 14
17 15
18 16 getActive() : IActivatable {
19 17 return this._active;
20 18 }
21 19
22 20 async deactivate() {
23 21 if (this._active)
24 22 await this._active.deactivate();
25 23 this._active = null;
26 24 }
27 25
28 26 async activate(component: IActivatable) {
29 27 if (!component || component.isActive())
30 28 return;
31 29 component.setActivationController(this);
32 30
33 31 await component.activate();
34 32 }
35 33
36 async activating(component: IActivatable, ct: ICancellation = EmptyCancellation.default) {
34 async activating(component: IActivatable, ct: ICancellation = Cancellation.none) {
37 35 if (component != this._active)
38 36 await this.deactivate();
39 37 }
40 38
41 async activated(component: IActivatable, ct: ICancellation = EmptyCancellation.default) {
39 async activated(component: IActivatable, ct: ICancellation = Cancellation.none) {
42 40 this._active = component;
43 41 }
44 42
45 async deactivating(component: IActivatable, ct: ICancellation = EmptyCancellation.default) {
43 async deactivating(component: IActivatable, ct: ICancellation = Cancellation.none) {
46 44
47 45 }
48 46
49 async deactivated(component: IActivatable, ct: ICancellation = EmptyCancellation.default) {
47 async deactivated(component: IActivatable, ct: ICancellation = Cancellation.none) {
50 48 if (this._active == component)
51 49 this._active = null;
52 50 }
53 51 }
54 52
55 53 tape('simple activation',async function(t){
56 54
57 55 let a = new SimpleActivatable();
58 56 t.false(a.isActive());
59 57
60 58 await a.activate();
61 59 t.true(a.isActive());
62 60
63 61 await a.deactivate();
64 62 t.false(a.isActive());
65 63
66 64 t.end();
67 65 });
68 66
69 67 tape('controller activation', async function(t) {
70 68
71 69 let a = new SimpleActivatable();
72 70 let c = new MockActivationController();
73 71
74 72 t.false(a.isActive(), "the component is not active by default");
75 73 t.assert(c.getActive() == null, "the activation controller doesn't have an active component by default");
76 74 t.assert(a.getActivationController() == null, "the component doesn't have an activation controller by default");
77 75
78 76 t.comment("Active the component through the controller");
79 77 await c.activate(a);
80 78 t.true(a.isActive(), "The component should successfully activate");
81 79 t.equal(c.getActive(), a, "The controller should point to the activated component");
82 80 t.equal(a.getActivationController(), c, "The component should point to the controller");
83 81
84 82 t.comment("Deactive the component throug the controller");
85 83 await c.deactivate();
86 84
87 85 t.false(a.isActive(), "The component should successfully deactivate");
88 86 t.equal(c.getActive(), null, "The controller shouldn't point to any component");
89 87 t.equal(a.getActivationController(), c, "The componet should point to it's controller");
90 88
91 89 t.end();
92 90 });
93 91
94 92 tape('handle error in onActivating', async function(t) {
95 93 let a = new SimpleActivatable();
96 94
97 95 a.onActivating = async function() {
98 96 throw "Should fail";
99 97 };
100 98
101 99 try {
102 100 await a.activate();
103 101 t.fail("activation should fail");
104 102 } catch {
105 103 }
106 104
107 105 t.false(a.isActive(), "the component should remain inactive");
108 106
109 107 t.end();
110 108 }); No newline at end of file
@@ -1,44 +1,62
1 1 import * as TraceSource from '@implab/core/log/TraceSource'
2 2 import * as tape from 'tape';
3 import * as ConsoleWriter from '@implab/core/log/writers/ConsoleWriter';
3 4
4 5 const sourceId = 'test/TraceSourceTests';
5 6
6 7 tape('trace message', t => {
7 8 let trace = TraceSource.get(sourceId);
8 9
9 10 trace.level = TraceSource.DebugLevel;
10 11
11 let h = trace.on((sender,level,msg) => {
12 t.equal(sender, trace, "sender should be the current trace source");
13 t.equal(TraceSource.DebugLevel, level, "level should be debug level");
14 t.equal(msg, "Hello, World!", "The message should be a formatted message");
12 let h = trace.on((ev) => {
13 t.equal(ev.source, trace, "sender should be the current trace source");
14 t.equal(ev.level, TraceSource.DebugLevel, "level should be debug level");
15 t.equal(ev.arg, "Hello, World!", "The message should be a formatted message");
15 16
16 17 t.end();
17 18 });
18 19
19 20 trace.debug("Hello, {0}!", "World");
20 21
21 22 h.destroy();
22 23 });
23 24
24 25 tape('trace event', t => {
25 26 let trace = TraceSource.get(sourceId);
26 27
27 28 trace.level = TraceSource.DebugLevel;
28 29
29 30 let event = {
30 31 name: "custom event"
31 32 };
32 33
33 let h = trace.on((sender,level,msg) => {
34 t.equal(sender, trace, "sender should be the current trace source");
35 t.equal(TraceSource.DebugLevel, level, "level should be debug level");
36 t.equal(msg, event, "The message should be the specified object");
34 let h = trace.on((ev) => {
35 t.equal(ev.source, trace, "sender should be the current trace source");
36 t.equal(ev.level, TraceSource.DebugLevel, "level should be debug level");
37 t.equal(ev.arg, event, "The message should be the specified object");
37 38
38 39 t.end();
39 40 });
40 41
41 42 trace.traceEvent(TraceSource.DebugLevel, event);
42 43
43 44 h.destroy();
45 });
46
47 tape('console writer', async t => {
48 let writer = new ConsoleWriter();
49
50 let trace = TraceSource.get(sourceId);
51 trace.level = TraceSource.DebugLevel;
52
53 let p = writer.write(trace);
54
55 trace.log("Hello, {0}!", 'World');
56 trace.warn("Look at me!");
57 trace.error("DIE!");
58
59 console.log("DONE");
60
61 t.end();
44 62 }); No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now