##// END OF EJS Templates
refactoring, all common interfaces placed to core/interfaces.ts...
cin -
r13:745612edbd74 propose cancellat...
parent child
Show More
@@ -0,0 +1,33
1 import { ICancellation } from "./interfaces";
2
3 export class Cancellation implements ICancellation {
4 isSupported(): boolean {
5 return false;
6 }
7 throwIfRequested(): void {
8 }
9
10 isRequested(): boolean {
11 return false;
12 }
13
14 register(_cb: (e:any) => void): void {
15
16 }
17
18 static readonly none : Cancellation = {
19 isSupported(): boolean {
20 return false;
21 },
22
23 throwIfRequested(): void {
24 },
25
26 isRequested(): boolean {
27 return false;
28 },
29
30 register(_cb: (e:any) => void): void {
31 }
32 };
33 } No newline at end of file
@@ -0,0 +1,85
1 import { IObservable, IObserver, IDestroyable, ICancellation } from '../interfaces';
2 import { Cancellation } from '../Cancellation'
3 import * as TraceSource from '../log/TraceSource'
4 import { argumentNotNull } from '../safe';
5
6 const trace = TraceSource.get('@implab/core/components/Observable');
7
8
9 class Observable<T> implements IObservable<T> {
10 private _once = new Array<IObserver<T>>();
11
12 private readonly _observers = new Array<IObserver<T>>();
13
14 constructor(func: (notify: IObserver<T>) => void) {
15 argumentNotNull(func, "func");
16
17 func(this._notify.bind(this));
18 }
19
20 on(observer: IObserver<T>): IDestroyable {
21 argumentNotNull(observer, "observer");
22
23 this._observers.push(observer);
24
25 let me = this;
26 return {
27 destroy() {
28 me._removeObserver(observer);
29 }
30 }
31 }
32
33 wait(ct: ICancellation = Cancellation.none): Promise<T> {
34 return new Promise<T>((resolve, reject) => {
35 this._once.push(resolve);
36 if (ct.isSupported()) {
37 ct.register((e) => {
38 this._removeOnce(resolve);
39 reject(e);
40 });
41 }
42 });
43 }
44
45 onObserverException(e: any) {
46 trace.error("Unhandled exception in the observer: {0}", e);
47 }
48
49 private _removeOnce(d: IObserver<T>) {
50 let i = this._once.indexOf(d);
51 if (i >= 0)
52 this._once.splice(i);
53 }
54
55 private _removeObserver(d: IObserver<T>) {
56 let i = this._observers.indexOf(d);
57 if (i >= 0)
58 this._observers.splice(i);
59 }
60
61 private _notify(evt: T) {
62 let guard = (observer: IObserver<T>) => {
63 try {
64 observer(evt);
65 } catch (e) {
66 this.onObserverException(e);
67 }
68 }
69
70 if (this._once.length) {
71 for (let i = 0; i < this._once.length; i++)
72 guard(this._once[i]);
73 this._once = [];
74 }
75
76 for (let i = 0; i < this._observers.length; i++)
77 guard(this._observers[i]);
78 }
79 }
80
81 namespace Observable {
82 export const traceSource = trace;
83 }
84
85 export = Observable; No newline at end of file
@@ -0,0 +1,83
1 import { watchFile } from "fs";
2
3 export interface IDestroyable {
4 destroy();
5 }
6
7 export interface ICancellation {
8 throwIfRequested(): void;
9 isRequested(): boolean;
10 isSupported(): boolean;
11 register(cb: (e: any) => void): void;
12 }
13
14 /**
15 * Интерфейс поддерживающий асинхронную активацию
16 */
17 export interface IActivatable {
18 /**
19 * @returns Boolean indicates the current state
20 */
21 isActive(): boolean;
22
23 /**
24 * Starts the component activation
25 * @param ct cancellation token for this operation
26 */
27 activate(ct?: ICancellation) : Promise<void>;
28
29 /**
30 * Starts the component deactivation
31 * @param ct cancellation token for this operation
32 */
33 deactivate(ct?: ICancellation) : Promise<void>;
34
35 /**
36 * Sets the activation controller for this component
37 * @param controller The activation controller
38 *
39 * Activation controller checks whether this component
40 * can be activated and manages the active state of the
41 * component
42 */
43 setActivationController(controller: IActivationController);
44
45 /**
46 * Gets the current activation controller for this component
47 */
48 getActivationController(): IActivationController;
49 }
50
51 export interface IActivationController {
52 activating(component: IActivatable, ct?: ICancellation): Promise<void>;
53
54 activated(component: IActivatable, ct?: ICancellation): Promise<void>;
55
56 deactivating(component: IActivatable, ct?: ICancellation): Promise<void>;
57
58 deactivated(component: IActivatable, ct?: ICancellation): Promise<void>;
59
60 deactivate(ct?: ICancellation): Promise<void>;
61
62 activate(component: IActivatable, ct?: ICancellation): Promise<void>;
63
64 getActive(): IActivatable;
65 }
66
67 export interface IAsyncComponent {
68 getCompletion(): Promise<void>;
69 }
70
71 export interface ICancellable {
72 cancel(reason?: any): void;
73 }
74
75 export interface IObserver<T> {
76 (x:T): void;
77 }
78
79 export interface IObservable<T> {
80 on(observer: IObserver<T>): IDestroyable;
81
82 wait(ct?: ICancellation) : Promise<T>;
83 } No newline at end of file
@@ -1,95 +1,87
1 import { IActivationController } from './IActivationController';
1 import { IActivationController, IActivatable, ICancellation } from '../interfaces';
2 import { IActivatable } from './IActivatable';
3 import { AsyncComponent } from './AsyncComponent';
2 import { AsyncComponent } from './AsyncComponent';
4 import { ICancellation } from '../ICancellation';
3 import { Cancellation } from '../Cancellation';
5 import { EmptyCancellation } from '../EmptyCancellation';
6 import * as TraceSource from '../log/TraceSource';
4 import * as TraceSource from '../log/TraceSource';
7
5
8 type Constructor<T = {}> = new (...args: any[]) => T;
6 type Constructor<T = {}> = new (...args: any[]) => T;
9
7
10 const log = TraceSource.get('@implab/core/components/ActivatableMixin');
8 const log = TraceSource.get('@implab/core/components/ActivatableMixin');
11
9
12 function ActivatableMixin<TBase extends Constructor<AsyncComponent>>(Base: TBase) {
10 function ActivatableMixin<TBase extends Constructor<AsyncComponent>>(Base: TBase) {
13 return class extends Base implements IActivatable {
11 return class extends Base implements IActivatable {
14 _controller: IActivationController;
12 _controller: IActivationController;
15
13
16 _active: boolean;
14 _active: boolean;
17
15
18 isActive() {
16 isActive() {
19 return this._active;
17 return this._active;
20 }
18 }
21
19
22 getActivationController() {
20 getActivationController() {
23 return this._controller;
21 return this._controller;
24 }
22 }
25
23
26 setActivationController(controller: IActivationController) {
24 setActivationController(controller: IActivationController) {
27 this._controller = controller;
25 this._controller = controller;
28 }
26 }
29
27
30 async onActivating(ct: ICancellation) {
28 async onActivating(ct: ICancellation) {
31 if (this._controller)
29 if (this._controller)
32 await this._controller.activating(this, ct);
30 await this._controller.activating(this, ct);
33 }
31 }
34
32
35 async onActivated(ct: ICancellation) {
33 async onActivated(ct: ICancellation) {
36 if (this._controller)
34 if (this._controller)
37 await this._controller.activated(this, ct);
35 await this._controller.activated(this, ct);
38 }
36 }
39
37
40 async activate(ct: ICancellation = EmptyCancellation.default) {
38 activate(ct: ICancellation = Cancellation.none) {
39 return this.runOperation(this._activateAsync.bind(this), ct);
40 }
41
42 async _activateAsync(ct: ICancellation) {
41 if (this.isActive())
43 if (this.isActive())
42 return;
44 return;
43 ct = this.startOperation(ct);
45
46 await this.onActivating(ct);
47 this._active = true;
44 try {
48 try {
45 await this.onActivating(ct);
49 await this.onActivated(ct);
46 this._active = true;
47 try {
48 await this.onActivated(ct);
49 } catch(e) {
50 log.error("Suppressed onActivated error: {0}", e);
51 }
52 this.completeSuccess();
53 } catch (e) {
50 } catch (e) {
54 this.completeFail(e);
51 log.error("Suppressed onActivated error: {0}", e);
55 }
52 }
56 return this.getCompletion();
57 }
53 }
58
54
59 async onDeactivating(ct: ICancellation) {
55 async onDeactivating(ct: ICancellation) {
60 if (this._controller)
56 if (this._controller)
61 await this._controller.deactivating(this, ct);
57 await this._controller.deactivating(this, ct);
62 }
58 }
63
59
64 async onDeactivated(ct: ICancellation) {
60 async onDeactivated(ct: ICancellation) {
65 if (this._controller)
61 if (this._controller)
66 await this._controller.deactivated(this, ct);
62 await this._controller.deactivated(this, ct);
67 }
63 }
68
64
69 async deactivate(ct: ICancellation = EmptyCancellation.default) {
65 deactivate(ct: ICancellation = Cancellation.none) {
66 return this.runOperation(this._deactivateAsync.bind(this), ct);
67 }
68
69 async _deactivateAsync(ct: ICancellation) {
70 if (!this.isActive())
70 if (!this.isActive())
71 return;
71 return;
72 ct = this.startOperation(ct);
72 await this.onDeactivating(ct);
73 this._active = false;
73 try {
74 try {
74 await this.onDeactivating(ct);
75 await this.onDeactivated(ct);
75 this._active = false;
76 try {
77 await this.onDeactivated(ct);
78 } catch(e) {
79 log.error("Suppressed onDeactivated error: {0}", e);
80 }
81 this.completeSuccess();
82 } catch (e) {
76 } catch (e) {
83 this.completeFail(e);
77 log.error("Suppressed onDeactivated error: {0}", e);
84 }
78 }
85 return this.getCompletion();
86 }
79 }
87
88 }
80 }
89 }
81 }
90
82
91 namespace ActivatableMixin {
83 namespace ActivatableMixin {
92 export const traceSource = log;
84 export const traceSource = log;
93 }
85 }
94
86
95 export = ActivatableMixin; No newline at end of file
87 export = ActivatableMixin;
@@ -1,47 +1,17
1 import { ICancellation } from "../ICancellation";
1 import { Cancellation } from "../Cancellation";
2 import { EmptyCancellation } from "../EmptyCancellation";
2 import { IAsyncComponent, ICancellation } from "../interfaces";
3
3
4 export class AsyncComponent {
4 export class AsyncComponent implements IAsyncComponent {
5 _completion: Promise<void>;
5 _completion: Promise<void> = Promise.resolve();
6
7 _deferred: {
8 resolve(): void
9 reject(reason: any): void
10 };
11
6
12 getCompletion() { return this._completion };
7 getCompletion() { return this._completion };
13
8
14 startOperation(ct: ICancellation = EmptyCancellation.default) {
9 runOperation(op: (ct: ICancellation) => any, ct: ICancellation = Cancellation.none) {
15 if (this._deferred)
10 // TODO create cancellation source here
16 throw new Error("The async operation is already pending");
11 async function guard() {
17
12 await op(ct);
18 this._completion = new Promise<void>((resolve, reject) => {
13 }
19 this._deferred = {
20 resolve: resolve,
21 reject: reject
22 }
23 });
24 return ct;
25 }
26
14
27 completeSuccess() {
15 return this._completion = guard();
28 this._deferred.resolve();
29 this._deferred = null;
30 }
31
32 completeFail(reason: any) {
33 this._deferred.reject(reason);
34 this._deferred = null;
35 }
36
37 async runOperation(cb: (ct: ICancellation) => Promise<void>, ct: ICancellation = EmptyCancellation.default) {
38 //safe.argumentNotNull(cb, "cb")
39 ct = this.startOperation(ct);
40 try {
41 await cb(ct);
42 this.completeSuccess();
43 } catch(e) {
44 this.completeFail(e);
45 }
46 }
16 }
47 } No newline at end of file
17 }
@@ -1,110 +1,110
1 import * as tape from 'tape';
1 import * as tape from 'tape';
2 import * as ActivatableMixin from '@implab/core/components/ActivatableMixin';
2 import * as ActivatableMixin from '@implab/core/components/ActivatableMixin';
3 import { AsyncComponent } from '@implab/core/components/AsyncComponent';
3 import { AsyncComponent } from '@implab/core/components/AsyncComponent';
4 import { IActivationController } from '@implab/core/components/IActivationController';
4 import { IActivationController } from '@implab/core/components/IActivationController';
5 import { IActivatable } from '@implab/core/components/IActivatable';
5 import { IActivatable } from '@implab/core/components/IActivatable';
6 import { ICancellation } from '@implab/core/ICancellation';
6 import { ICancellation } from '@implab/core/ICancellation';
7 import { EmptyCancellation } from '@implab/core/EmptyCancellation';
7 import { EmptyCancellation } from '@implab/core/EmptyCancellation';
8
8
9 class SimpleActivatable extends ActivatableMixin(AsyncComponent) {
9 class SimpleActivatable extends ActivatableMixin(AsyncComponent) {
10
10
11 }
11 }
12
12
13 class MockActivationController implements IActivationController {
13 class MockActivationController implements IActivationController {
14
14
15 _active: IActivatable = null;
15 _active: IActivatable = null;
16
16
17
17
18 getActive() : IActivatable {
18 getActive() : IActivatable {
19 return this._active;
19 return this._active;
20 }
20 }
21
21
22 async deactivate() {
22 async deactivate() {
23 if (this._active)
23 if (this._active)
24 await this._active.deactivate();
24 await this._active.deactivate();
25 this._active = null;
25 this._active = null;
26 }
26 }
27
27
28 async activate(component: IActivatable) {
28 async activate(component: IActivatable) {
29 if (!component || component.isActive())
29 if (!component || component.isActive())
30 return;
30 return;
31 component.setActivationController(this);
31 component.setActivationController(this);
32
32
33 await component.activate();
33 await component.activate();
34 }
34 }
35
35
36 async activating(component: IActivatable, ct: ICancellation = EmptyCancellation.default) {
36 async activating(component: IActivatable, ct: ICancellation = EmptyCancellation.default) {
37 if (component != this._active)
37 if (component != this._active)
38 await this.deactivate();
38 await this.deactivate();
39 }
39 }
40
40
41 async activated(component: IActivatable, ct: ICancellation = EmptyCancellation.default) {
41 async activated(component: IActivatable, ct: ICancellation = EmptyCancellation.default) {
42 this._active = component;
42 this._active = component;
43 }
43 }
44
44
45 async deactivating(component: IActivatable, ct: ICancellation = EmptyCancellation.default) {
45 async deactivating(component: IActivatable, ct: ICancellation = EmptyCancellation.default) {
46
46
47 }
47 }
48
48
49 async deactivated(component: IActivatable, ct: ICancellation = EmptyCancellation.default) {
49 async deactivated(component: IActivatable, ct: ICancellation = EmptyCancellation.default) {
50 if (this._active == component)
50 if (this._active == component)
51 this._active = null;
51 this._active = null;
52 }
52 }
53 }
53 }
54
54
55 tape('simple activation',async function(t){
55 tape('simple activation',async function(t){
56
56
57 let a = new SimpleActivatable();
57 let a = new SimpleActivatable();
58 t.false(a.isActive());
58 t.false(a.isActive());
59
59
60 await a.activate();
60 await a.activate();
61 t.true(a.isActive());
61 t.true(a.isActive());
62
62
63 await a.deactivate();
63 await a.deactivate();
64 t.false(a.isActive());
64 t.false(a.isActive());
65
65
66 t.end();
66 t.end();
67 });
67 });
68
68
69 tape('controller activation', async function(t) {
69 tape('controller activation', async function(t) {
70
70
71 let a = new SimpleActivatable();
71 let a = new SimpleActivatable();
72 let c = new MockActivationController();
72 let c = new MockActivationController();
73
73
74 t.false(a.isActive(), "the component is not active by default");
74 t.false(a.isActive(), "the component is not active by default");
75 t.assert(c.getActive() == null, "the activation controller doesn't have an active component by default");
75 t.assert(c.getActive() == null, "the activation controller doesn't have an active component by default");
76 t.assert(a.getActivationController() == null, "the component doesn't have an activation controller by default");
76 t.assert(a.getActivationController() == null, "the component doesn't have an activation controller by default");
77
77
78 t.comment("Active the component through the controller");
78 t.comment("Active the component through the controller");
79 await c.activate(a);
79 await c.activate(a);
80 t.true(a.isActive(), "The component should successfully activate");
80 t.true(a.isActive(), "The component should successfully activate");
81 t.assert(c.getActive() == a, "The controller should point to the activated component");
81 t.equal(c.getActive(), a, "The controller should point to the activated component");
82 t.assert(a.getActivationController() == c, "The component should point to the controller");
82 t.equal(a.getActivationController(), c, "The component should point to the controller");
83
83
84 t.comment("Deactive the component throug the controller");
84 t.comment("Deactive the component throug the controller");
85 await c.deactivate();
85 await c.deactivate();
86
86
87 t.false(a.isActive(), "The component should successfully deactivate");
87 t.false(a.isActive(), "The component should successfully deactivate");
88 t.assert(c.getActive() == null, "The controller shouldn't point to any component");
88 t.equal(c.getActive(), null, "The controller shouldn't point to any component");
89 t.assert(a.getActivationController() == c, "The componet should point to it's controller");
89 t.equal(a.getActivationController(), c, "The componet should point to it's controller");
90
90
91 t.end();
91 t.end();
92 });
92 });
93
93
94 tape('handle error in onActivating', async function(t) {
94 tape('handle error in onActivating', async function(t) {
95 let a = new SimpleActivatable();
95 let a = new SimpleActivatable();
96
96
97 a.onActivating = async function() {
97 a.onActivating = async function() {
98 throw "Should fail";
98 throw "Should fail";
99 };
99 };
100
100
101 try {
101 try {
102 await a.activate();
102 await a.activate();
103 t.fail("activation should fail");
103 t.fail("activation should fail");
104 } catch {
104 } catch {
105 }
105 }
106
106
107 t.false(a.isActive(), "the component should remain inactive");
107 t.false(a.isActive(), "the component should remain inactive");
108
108
109 t.end();
109 t.end();
110 }); No newline at end of file
110 });
@@ -1,22 +1,44
1 import * as TraceSource from '@implab/core/log/TraceSource'
1 import * as TraceSource from '@implab/core/log/TraceSource'
2 import * as tape from 'tape';
2 import * as tape from 'tape';
3
3
4 const sourceId = 'test/TraceSourceTests';
4 const sourceId = 'test/TraceSourceTests';
5
5
6 tape('', t => {
6 tape('trace message', t => {
7 let trace = TraceSource.get(sourceId);
7 let trace = TraceSource.get(sourceId);
8
8
9 trace.level = TraceSource.DebugLevel;
9 trace.level = TraceSource.DebugLevel;
10
10
11 let h = trace.on((sender,level,msg) => {
11 let h = trace.on((sender,level,msg) => {
12 t.equal(sender, trace, "sender should be the current trace source");
12 t.equal(sender, trace, "sender should be the current trace source");
13 t.equal(TraceSource.DebugLevel, level, "level should be debug level");
13 t.equal(TraceSource.DebugLevel, level, "level should be debug level");
14 t.equal(msg, "Hello, World!", "The message should be formatted correctly");
14 t.equal(msg, "Hello, World!", "The message should be a formatted message");
15
15
16 t.end();
16 t.end();
17 });
17 });
18
18
19 trace.debug("Hello, {0}!", "World");
19 trace.debug("Hello, {0}!", "World");
20
20
21 h.destroy();
21 h.destroy();
22 });
23
24 tape('trace event', t => {
25 let trace = TraceSource.get(sourceId);
26
27 trace.level = TraceSource.DebugLevel;
28
29 let event = {
30 name: "custom event"
31 };
32
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");
37
38 t.end();
39 });
40
41 trace.traceEvent(TraceSource.DebugLevel, event);
42
43 h.destroy();
22 }); No newline at end of file
44 });
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now