2020-11-19 16:51:14 +00:00
|
|
|
import { GameObject } from './public/GameObject';
|
2023-11-21 13:57:06 +00:00
|
|
|
import { PCode, PrimFlags, UUID } from '..';
|
2020-11-19 16:51:14 +00:00
|
|
|
import { Region } from './Region';
|
2023-11-21 13:57:06 +00:00
|
|
|
import { Subscription } from 'rxjs';
|
2023-11-16 22:56:20 +00:00
|
|
|
import { GetObjectsOptions } from './commands/RegionCommands';
|
2023-11-21 13:57:06 +00:00
|
|
|
import { ObjectResolvedEvent } from '../events/ObjectResolvedEvent';
|
|
|
|
|
import { clearTimeout } from 'timers';
|
|
|
|
|
import { BatchQueue } from './BatchQueue';
|
2020-12-03 13:55:02 +00:00
|
|
|
|
2020-11-19 16:51:14 +00:00
|
|
|
export class ObjectResolver
|
|
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
private resolveQueue = new BatchQueue<GameObject>(256, this.resolveInternal.bind(this));
|
|
|
|
|
private getCostsQueue = new BatchQueue<GameObject>(256, this.getCostsInternal.bind(this));
|
2020-11-19 16:51:14 +00:00
|
|
|
|
2022-04-20 11:32:38 +01:00
|
|
|
constructor(private region?: Region)
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-21 13:57:06 +00:00
|
|
|
public async resolveObjects(objects: GameObject[], options: GetObjectsOptions): Promise<GameObject[]>
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
if (!this.region)
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
throw new Error('Region is going away');
|
|
|
|
|
}
|
2020-11-19 16:51:14 +00:00
|
|
|
|
2023-11-21 13:57:06 +00:00
|
|
|
// First, create a map of all object IDs
|
|
|
|
|
const objs = new Map<number, GameObject>();
|
|
|
|
|
const failed: GameObject[] = [];
|
|
|
|
|
for (const obj of objects)
|
|
|
|
|
{
|
2023-11-21 14:33:12 +00:00
|
|
|
if (!obj.IsAttachment && !options.includeTempObjects && ((obj.Flags ?? 0) & PrimFlags.TemporaryOnRez) === PrimFlags.TemporaryOnRez)
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
continue;
|
2020-11-19 16:51:14 +00:00
|
|
|
}
|
2023-11-21 13:57:06 +00:00
|
|
|
if (!options.includeAvatars && obj.PCode === PCode.Avatar)
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
continue;
|
2020-11-19 16:51:14 +00:00
|
|
|
}
|
2023-11-21 13:57:06 +00:00
|
|
|
this.region.objects.populateChildren(obj);
|
|
|
|
|
this.scanObject(obj, objs);
|
|
|
|
|
}
|
2020-11-19 16:51:14 +00:00
|
|
|
|
2023-11-21 13:57:06 +00:00
|
|
|
if (objs.size === 0)
|
|
|
|
|
{
|
|
|
|
|
return failed;
|
|
|
|
|
}
|
2020-11-19 16:51:14 +00:00
|
|
|
|
2023-11-21 14:33:12 +00:00
|
|
|
return await this.resolveQueue.add(Array.from(objs.values()));
|
2023-11-21 13:57:06 +00:00
|
|
|
}
|
2020-11-19 16:51:14 +00:00
|
|
|
|
2023-11-21 13:57:06 +00:00
|
|
|
public async getInventory(object: GameObject): Promise<void>
|
|
|
|
|
{
|
|
|
|
|
await this.getInventories([object]);
|
|
|
|
|
}
|
2020-11-19 16:51:14 +00:00
|
|
|
|
2023-11-21 13:57:06 +00:00
|
|
|
public async getInventories(objects: GameObject[]): Promise<void>
|
|
|
|
|
{
|
|
|
|
|
for (const obj of objects)
|
|
|
|
|
{
|
|
|
|
|
if (!obj.resolvedInventory)
|
2022-04-20 11:37:38 +01:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
await obj.updateInventory();
|
2022-04-20 11:37:38 +01:00
|
|
|
}
|
2023-11-21 13:57:06 +00:00
|
|
|
}
|
|
|
|
|
}
|
2020-11-19 16:51:14 +00:00
|
|
|
|
2023-11-21 13:57:06 +00:00
|
|
|
public async getCosts(objects: GameObject[]): Promise<void>
|
|
|
|
|
{
|
|
|
|
|
await this.getCostsQueue.add(objects);
|
|
|
|
|
}
|
2020-11-19 16:51:14 +00:00
|
|
|
|
2023-11-21 13:57:06 +00:00
|
|
|
public shutdown(): void
|
|
|
|
|
{
|
|
|
|
|
delete this.region;
|
2020-11-19 16:51:14 +00:00
|
|
|
}
|
|
|
|
|
|
2023-11-21 13:57:06 +00:00
|
|
|
private scanObject(obj: GameObject, map: Map<number, GameObject>): void
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
|
|
|
|
const localID = obj.ID;
|
2023-11-21 13:57:06 +00:00
|
|
|
if (!map.has(localID))
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
map.set(localID, obj);
|
2020-11-19 16:51:14 +00:00
|
|
|
if (obj.children)
|
|
|
|
|
{
|
|
|
|
|
for (const child of obj.children)
|
|
|
|
|
{
|
|
|
|
|
this.scanObject(child, map);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-21 13:57:06 +00:00
|
|
|
private async resolveInternal(objs: Set<GameObject>): Promise<Set<GameObject>>
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
if (!this.region)
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
throw new Error('Region went away');
|
2020-11-19 16:51:14 +00:00
|
|
|
}
|
2023-11-21 13:57:06 +00:00
|
|
|
|
|
|
|
|
const objArray = Array.from(objs.values());
|
2020-11-19 16:51:14 +00:00
|
|
|
try
|
|
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
await this.region.clientCommands.region.selectObjects(objArray);
|
2020-11-19 16:51:14 +00:00
|
|
|
}
|
2023-11-21 13:57:06 +00:00
|
|
|
finally
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
await this.region.clientCommands.region.deselectObjects(objArray);
|
2020-11-19 16:51:14 +00:00
|
|
|
}
|
2023-11-21 13:57:06 +00:00
|
|
|
|
|
|
|
|
if (!this.region)
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
throw new Error('Region went away');
|
2020-11-19 16:51:14 +00:00
|
|
|
}
|
2023-11-21 13:57:06 +00:00
|
|
|
|
|
|
|
|
const objects = new Map<number, GameObject>();
|
|
|
|
|
for (const obj of objs.values())
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
objects.set(obj.ID, obj);
|
|
|
|
|
}
|
2021-09-23 17:14:23 +01:00
|
|
|
|
2023-11-21 13:57:06 +00:00
|
|
|
for (let x = 0; x < 3; x++)
|
|
|
|
|
{
|
|
|
|
|
try
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
await this.waitForResolve(objects, 10000);
|
|
|
|
|
}
|
|
|
|
|
catch (_e)
|
|
|
|
|
{
|
|
|
|
|
// Ignore
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const failed = new Set<GameObject>();
|
|
|
|
|
for (const o of objects.values())
|
|
|
|
|
{
|
|
|
|
|
failed.add(o);
|
2020-11-19 16:51:14 +00:00
|
|
|
}
|
2023-11-21 13:57:06 +00:00
|
|
|
|
|
|
|
|
return failed;
|
2020-11-19 16:51:14 +00:00
|
|
|
}
|
|
|
|
|
|
2023-11-21 13:57:06 +00:00
|
|
|
private async getCostsInternal(objs: Set<GameObject>): Promise<Set<GameObject>>
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
const failed = new Set<GameObject>();
|
|
|
|
|
|
|
|
|
|
const submitted: Map<string, GameObject> = new Map<string, GameObject>();
|
|
|
|
|
for (const obj of objs.values())
|
2022-04-20 11:32:38 +01:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
submitted.set(obj.FullID.toString(), obj);
|
2022-04-20 11:32:38 +01:00
|
|
|
}
|
|
|
|
|
|
2020-11-19 16:51:14 +00:00
|
|
|
try
|
|
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
if (!this.region)
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
return objs;
|
2020-11-19 16:51:14 +00:00
|
|
|
}
|
2023-11-21 13:57:06 +00:00
|
|
|
const result = await this.region.caps.capsPostXML('GetObjectCost', {
|
|
|
|
|
'object_ids': Array.from(submitted.keys())
|
|
|
|
|
});
|
|
|
|
|
const uuids = Object.keys(result);
|
|
|
|
|
for (const key of uuids)
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
const costs = result[key];
|
|
|
|
|
try
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
if (!this.region)
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
continue;
|
2020-11-19 16:51:14 +00:00
|
|
|
}
|
2023-11-21 13:57:06 +00:00
|
|
|
const obj: GameObject = this.region.objects.getObjectByUUID(new UUID(key));
|
|
|
|
|
obj.linkPhysicsImpact = parseFloat(costs['linked_set_physics_cost']);
|
|
|
|
|
obj.linkResourceImpact = parseFloat(costs['linked_set_resource_cost']);
|
|
|
|
|
obj.physicaImpact = parseFloat(costs['physics_cost']);
|
|
|
|
|
obj.resourceImpact = parseFloat(costs['resource_cost']);
|
|
|
|
|
obj.limitingType = costs['resource_limiting_type'];
|
2020-11-19 16:51:14 +00:00
|
|
|
|
2023-11-21 13:57:06 +00:00
|
|
|
|
|
|
|
|
obj.landImpact = Math.round(obj.linkPhysicsImpact);
|
|
|
|
|
if (obj.linkResourceImpact > obj.linkPhysicsImpact)
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
obj.landImpact = Math.round(obj.linkResourceImpact);
|
2020-11-19 16:51:14 +00:00
|
|
|
}
|
2023-11-21 13:57:06 +00:00
|
|
|
obj.calculatedLandImpact = obj.landImpact;
|
2023-11-21 14:33:12 +00:00
|
|
|
if (obj.Flags !== undefined && ((obj.Flags & PrimFlags.TemporaryOnRez) === PrimFlags.TemporaryOnRez) && obj.limitingType === 'legacy')
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
obj.calculatedLandImpact = 0;
|
2020-11-19 16:51:14 +00:00
|
|
|
}
|
2023-11-21 13:57:06 +00:00
|
|
|
submitted.delete(key);
|
|
|
|
|
}
|
|
|
|
|
catch (error)
|
|
|
|
|
{
|
2020-11-19 16:51:14 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-11-21 13:57:06 +00:00
|
|
|
catch (error)
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
|
|
|
|
}
|
2023-11-21 13:57:06 +00:00
|
|
|
|
|
|
|
|
for (const go of submitted.values())
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
failed.add(go);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return failed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async waitForResolve(objs: Map<number, GameObject>, timeout: number = 10000): Promise<void>
|
|
|
|
|
{
|
|
|
|
|
const entries = objs.entries();
|
|
|
|
|
for (const [localID, entry] of entries)
|
|
|
|
|
{
|
|
|
|
|
if (entry.resolvedAt !== undefined)
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
objs.delete(localID);
|
2020-11-19 16:51:14 +00:00
|
|
|
}
|
2023-11-21 13:57:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (objs.size === 0)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new Promise<void>((resolve, reject) =>
|
|
|
|
|
{
|
|
|
|
|
if (!this.region)
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
reject(new Error('Region went away'));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let subs: Subscription | undefined = undefined;
|
|
|
|
|
let timer: number | undefined = undefined;
|
|
|
|
|
subs = this.region.clientEvents.onObjectResolvedEvent.subscribe((obj: ObjectResolvedEvent) =>
|
|
|
|
|
{
|
|
|
|
|
objs.delete(obj.object.ID);
|
|
|
|
|
if (objs.size === 0)
|
2022-04-20 11:32:38 +01:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
if (timer !== undefined)
|
2023-11-16 23:40:33 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
clearTimeout(timer);
|
|
|
|
|
timer = undefined;
|
2023-11-16 23:40:33 +00:00
|
|
|
}
|
2023-11-21 13:57:06 +00:00
|
|
|
if (subs !== undefined)
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
subs.unsubscribe();
|
|
|
|
|
subs = undefined;
|
2020-11-19 16:51:14 +00:00
|
|
|
}
|
2023-11-21 13:57:06 +00:00
|
|
|
resolve();
|
2023-11-16 23:40:33 +00:00
|
|
|
}
|
2023-11-21 13:57:06 +00:00
|
|
|
});
|
|
|
|
|
timer = setTimeout(() =>
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
if (subs !== undefined)
|
2020-11-19 16:51:14 +00:00
|
|
|
{
|
2023-11-21 13:57:06 +00:00
|
|
|
subs.unsubscribe();
|
|
|
|
|
subs = undefined;
|
2020-11-19 16:51:14 +00:00
|
|
|
}
|
2023-11-21 13:57:06 +00:00
|
|
|
reject(new Error('Timeout'));
|
|
|
|
|
}, timeout) as unknown as number;
|
|
|
|
|
});
|
2022-04-20 11:32:38 +01:00
|
|
|
}
|
2020-11-19 16:51:14 +00:00
|
|
|
}
|