license import { DetectedImage } from '../../../defs/detected-image.js' ; import { Marker } from '../../../defs/marker.js' ; import { ArtifactDealer , ProbableTargets } from '../../../src/artifacts/artifact-dealer.js' ; import { ArtifactLoader } from '../../artifacts/artifact-loader.js' ; import { ARArtifact } from '../../artifacts/schema/extension-ar-artifacts.js' ; import { ArtifactStore , PerceptionResult , PerceptionState } from '../../artifacts/stores/artifact-store.js' ; import { LocalArtifactStore } from '../../artifacts/stores/local-artifact-store.js' ; import { generateMarkerId } from '../../utils/generate-marker-id.js' ; export type ShouldLoadArtifactsFromCallback = ( ( url : URL ) => boolean ) | string [ ] ; export interface PerceptionStateChangeRequest extends PerceptionState { shouldLoadArtifactsFrom ? : ShouldLoadArtifactsFromCallback ; } export interface PerceptionStateChangeResponse extends ProbableTargets { found : PerceptionResult [ ] ; lost : PerceptionResult [ ] ; newTargets : Array < Marker | DetectedImage > ; } hidden export class MeaningMaker { private _lastSeenTimeBuffer = 2000 ; private readonly artloader = new ArtifactLoader ( ) ; private readonly artstore = new LocalArtifactStore ( ) ; private readonly artdealer = new ArtifactDealer ( ) ; private prevPerceptionResults = new Set < PerceptionResult > ( ) ; private artifactsForUrl = new Map < string , ARArtifact [ ] > ( ) ; private lastSeenMarkers = new Map < string , { marker : Marker ; timestamp : number ; } > ( ) ; private lastSeenImages = new Map < string , { image : DetectedImage ; timestamp : number ; } > ( ) ; constructor ( ) { this . addArtifactStore ( this . artstore ) ; } /**
     * Load artifact content for initial set.
     */ async init ( ) { const artifacts = await this . artloader . fromElement ( document , document . URL ) ; this . artifactsForUrl . set ( document . URL , artifacts ) ; this . saveArtifacts ( artifacts ) ; } /**
     * Sets the amount of time (in ms) to wait after a target is last seen, before it is actually reported as lost.
     *
     * This is used so we do not spuriously lose/find targets just because they are not detected in a few frame captures.
     */ set lastSeenTimeBuffer ( ms ) { this . _lastSeenTimeBuffer = ms ; } /**
     * Gets the amount of time (in ms) to wait after a target is last seen, before it is actually reported as lost.
     */ get lastSeenTimeBuffer ( ) { return this . _lastSeenTimeBuffer ; } /**
     * Add another ArtifactStore to the ArtifactDealer.
     */ addArtifactStore ( store ) { this . artdealer . addArtifactStore ( store ) ; } /**
     * Load artifact content from Url, unconditionally.
     *
     * Usually this is used only for loading Urls which were explicitly configured by developer, on startup.
     * See `loadArtifactsFromSupportedUrl` (below) for the more common case.
     */ async loadArtifactsFromUrl ( url ) { if ( this . artifactsForUrl . has ( url . toString ( ) ) ) { return this . artifactsForUrl . get ( url . toString ( ) ) as ARArtifact [ ] ; } const artifacts = await this . artloader . fromUrl ( url ) ; this . saveArtifacts ( artifacts ) ; this . artifactsForUrl . set ( url . toString ( ) , artifacts ) ; return artifacts ; } /**
     * Load artifact content from url, only if Url is deemed appropriate (by config options).
     *
     * Usually this is used for Urls which not expected, but discovered (e.g. from URL-based Markers).
     *
     * By default, we restrict to same-origin, but you can pass a custom fn or list of domains.
     */ async loadArtifactsFromSupportedUrl ( url , shouldLoadArtifactsFrom ? ) { if ( ! shouldLoadArtifactsFrom ) { shouldLoadArtifactsFrom = ( url ) => url . origin === window . location . origin ; } else if ( Array . isArray ( shouldLoadArtifactsFrom ) ) { const origins = shouldLoadArtifactsFrom ; shouldLoadArtifactsFrom = ( url ) => ! ! origins . find ( o => o === url . origin ) ; } if ( ! shouldLoadArtifactsFrom ( url ) ) { return [ ] ; } return this . loadArtifactsFromUrl ( url ) ; } async updatePerceptionState ( request ) { const now = performance . now ( ) ; const newTargets = this . updateLastSeenTimes ( request , now ) ; this . removeStaleTargets ( now ) ; await this . tryIndexUrlMarkers ( request ) ; return { newTargets , ... await this . computeLostFoundResults ( request ) , ... await this . artdealer . predictPerceptionTargets ( request ) } ; } /**
     * Update lastSeen times for all targets which have been reported to be seen again.
     * Returns the list of brand new targets.
     */ private updateLastSeenTimes ( request , timestamp ) { const firstSeenTargets = [ ] ; for ( const marker of request . markers || [ ] ) { const markerId = generateMarkerId ( marker ) ; if ( ! this . lastSeenMarkers . has ( markerId ) ) { firstSeenTargets . push ( marker ) ; } this . lastSeenMarkers . set ( markerId , { marker , timestamp } ) ; } for ( const image of request . images || [ ] ) { const imageId = image . id ; if ( ! this . lastSeenMarkers . has ( imageId ) ) { firstSeenTargets . push ( image ) ; } this . lastSeenImages . set ( imageId , { image , timestamp } ) ; } return firstSeenTargets ; } /**
     * Remove all targets which we haven't seen for a while (as per lastSeenTimeBuffer).
     */ private removeStaleTargets ( now ) { for ( const [ markerId , { timestamp } ] of this . lastSeenMarkers . entries ( ) ) { if ( now - timestamp > this . lastSeenTimeBuffer ) { this . lastSeenMarkers . delete ( markerId ) ; } } for ( const [ imageId , { timestamp } ] of this . lastSeenImages . entries ( ) ) { if ( now - timestamp > this . lastSeenTimeBuffer ) { this . lastSeenImages . delete ( imageId ) ; } } } private async tryIndexUrlMarkers ( request ) { const tasks = [ ] ; for ( const marker of request . markers || [ ] ) { try { const url = new URL ( marker . value ) ; tasks . push ( this . loadArtifactsFromSupportedUrl ( url , request . shouldLoadArtifactsFrom ) ) ; } catch ( _ ) { } } return Promise . all ( tasks ) ; } /**
     * Based on the current nearby targets (which were already buffered with lastSeenTimeBuffer) compute the set of
     * Lost and Found results compared to the last time we reported.
     *
     * ArtifactDealer always returns the full set of results for a given context, so this maintains previous state and
     * computes a diff.
     */ private async computeLostFoundResults ( request ) { const state = { markers : Array . from ( this . lastSeenMarkers . values ( ) , ( { marker } ) => marker ) , geo : request . geo , images : Array . from ( this . lastSeenImages . values ( ) , ( { image } ) => image ) } ; const nearbyResults = await this . artdealer . getPerceptionResults ( state ) ; const uniqueNearbyResults = new Set ( nearbyResults ) ; const found = [ ... uniqueNearbyResults ] . filter ( a => ! this . prevPerceptionResults . has ( a ) ) ; const lost = [ ... this . prevPerceptionResults ] . filter ( a => ! uniqueNearbyResults . has ( a ) ) ; this . prevPerceptionResults = uniqueNearbyResults ; return { lost , found } ; } private saveArtifacts ( artifacts ) { for ( const artifact of artifacts ) { this . artstore . addArtifact ( artifact ) ; } } }