import { merge , Observable , Subscription } from "rxjs" ; import { filter , map , share } from "rxjs/operators" ; import { AdvertiseEvent , CoatyObject , CoreType , DeadvertiseEvent , DiscoverEvent , equals , ResolveEvent , Uuid , } from ".." ; import { Controller } from "./controller" ; /**
 * Represents changes in the objects kept for lifecycle management by the
 * `ObjectLifecycleController`. Changes are provided by the properties `added`,
 * `removed`, or `changed` where exactly one is set and the others are not
 * defined.
 */ export interface ObjectLifecycleInfo { /**
     * Array of objects which have been newly advertised or discovered
     * (optional). The value is not defined if no additions occured.
     */ added ? : CoatyObject [ ] ; /**
     * Array of objects which have been readvertised or rediscovered with
     * different object properties (optional). The value is not defined if no
     * changes occured.
     */ changed ? : CoatyObject [ ] ; /**
     * Array of objects which have been deadvertised (optional). The value is
     * not defined if no removals occured.
     */ removed ? : CoatyObject [ ] ; } /**
 * Keeps track of agents or specific Coaty objects in a Coaty network by
 * monitoring agent identities or custom object types.
 *
 * This controller observes advertisements and deadvertisements of such objects
 * and discovers them. Changes are emitted on corresponding observables that
 * applications can subscribe to.
 *
 * You can use this controller either standalone by adding it to the container
 * components or extend your custom controller class from this controller class.
 *
 * If you want to keep track of custom object types (not agent identities), you
 * have to implement the remote side of the distributed object lifecycle
 * management explicitely, i.e. advertise/readvertise/deadvertise your custom
 * objects and observe/resolve corresponding Discover events. To facilitate
 * this, this controller provides convenience methods:
 * `advertiseDiscoverableObject`, `readvertiseDiscoverableObject`, and
 * `deadvertiseDiscoverableObject`.
 *
 * Usually, a custom object should have the object ID of its agent identity,
 * i.e. `Container.identity`, set as its `parentObjectId` in order to be
 * automatically deadvertised when the agent terminates abnormally. You can
 * automate this by passing `true` to the optional parameter
 * `shouldSetParentObjectId` of method `advertiseDiscoverableObject` (`true` is
 * also the default parameter value).
 */ export class ObjectLifecycleController extends Controller { remarks param coreType param objectFilter returns observeObjectLifecycleInfoByCoreType ( coreType , objectFilter ? ) { return this . _observeObjectLifecycleInfo ( coreType , undefined , objectFilter ) ; } remarks param objectType param objectFilter returns observeObjectLifecycleInfoByObjectType ( objectType , objectFilter ? ) { return this . _observeObjectLifecycleInfo ( undefined , objectType , objectFilter ) ; } param object param shouldSetParentObjectId returns advertiseDiscoverableObject ( object , shouldSetParentObjectId = true ) { if ( shouldSetParentObjectId ) { object . parentObjectId = this . container . identity . objectId ; } this . communicationManager . publishAdvertise ( AdvertiseEvent . withObject ( object ) ) ; return this . communicationManager . observeDiscover ( ) . pipe ( filter ( ( event ) => ( event . data . isDiscoveringTypes && event . data . isObjectTypeCompatible ( object . objectType ) ) || ( event . data . isDiscoveringObjectId && event . data . objectId === object . objectId ) ) ) . subscribe ( event => event . resolve ( ResolveEvent . withObject ( object ) ) ) ; } param object readvertiseDiscoverableObject ( object ) { this . communicationManager . publishAdvertise ( AdvertiseEvent . withObject ( object ) ) ; } param object param discoverableSubscription deadvertiseDiscoverableObject ( object , discoverableSubscription ) { discoverableSubscription ?. unsubscribe ( ) ; this . communicationManager . publishDeadvertise ( DeadvertiseEvent . withObjectIds ( object . objectId ) ) ; } private _observeObjectLifecycleInfo ( coreType ? , objectType ? , objectFilter ? ) { if ( ! coreType && ! objectType ) { coreType = "Identity" ; } if ( coreType && objectType ) { throw new TypeError ( "Must not specify both coreType and objectType" ) ; } const registry = new Map < Uuid , CoatyObject > ( ) ; const discovered = this . communicationManager . publishDiscover ( coreType ? DiscoverEvent . withCoreTypes ( [ coreType ] ) : DiscoverEvent . withObjectTypes ( [ objectType ] ) ) ; const advertised = coreType ? this . communicationManager . observeAdvertiseWithCoreType ( coreType ) : this . communicationManager . observeAdvertiseWithObjectType ( objectType ) ; const deadvertised = this . communicationManager . observeDeadvertise ( ) ; return merge ( merge ( discovered , advertised ) . pipe ( map ( event => event . data . object ) , filter ( obj => obj !== undefined && ( objectFilter ? objectFilter ( obj ) === true : true ) ) , map ( obj => this . _addToRegistry ( registry , obj ) ) , filter ( info => info !== undefined ) ) , deadvertised . pipe ( map ( event => this . _removeFromRegistry ( registry , event . data . objectIds ) ) , filter ( info => info !== undefined ) ) ) . pipe ( share ( ) ) ; } private _addToRegistry ( registry , obj ) { const robj = registry . get ( obj . objectId ) ; registry . set ( obj . objectId , obj ) ; if ( robj === undefined ) { return { added : [ obj ] , } ; } else if ( ! equals ( obj , robj ) ) { return { changed : [ obj ] , } ; } else { return undefined ; } } private _removeFromRegistry ( registry , objectIds ) { const removed = [ ] ; objectIds . forEach ( objectId => this . _updateRegistryOnRemove ( registry , objectId , removed ) ) ; if ( removed . length === 0 ) { return undefined ; } return { removed , } ; } private _updateRegistryOnRemove ( registry , objectId , removed ) { const obj = registry . get ( objectId ) ; registry . forEach ( o => { if ( o . parentObjectId === objectId ) { registry . delete ( o . objectId ) ; removed . push ( o ) ; } } ) ; if ( obj !== undefined ) { registry . delete ( objectId ) ; removed . push ( obj ) ; } } }