Files
GDevelop/GDJS/Runtime/RuntimeInstanceContainer.ts

825 lines
28 KiB
TypeScript

/*
* GDevelop JS Platform
* Copyright 2013-2016 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
* This project is released under the MIT License.
*/
namespace gdjs {
const logger = new gdjs.Logger('RuntimeInstanceContainer');
/**
* A container of object instances rendered on screen.
*/
export abstract class RuntimeInstanceContainer {
_initialBehaviorSharedData: Hashtable<BehaviorSharedData | null>;
/** Contains the instances living on the container */
_instances: Hashtable<RuntimeObject[]>;
/**
* An array used to create a list of all instance when necessary.
* @see gdjs.RuntimeInstanceContainer#_constructListOfAllInstances}
*/
private _allInstancesList: gdjs.RuntimeObject[] = [];
_allInstancesListIsUpToDate = true;
/** Used to recycle destroyed instance instead of creating new ones. */
_instancesCache: Hashtable<RuntimeObject[]>;
/** The instances removed from the container and waiting to be sent to the cache. */
_instancesRemoved: gdjs.RuntimeObject[] = [];
/** Contains the objects data stored in the project */
_objects: Hashtable<ObjectData>;
_objectsCtor: Hashtable<typeof RuntimeObject>;
_layers: Hashtable<RuntimeLayer>;
_orderedLayers: RuntimeLayer[]; // TODO: should this be a single structure with _layers, to enforce its usage?
_layersCameraCoordinates: Record<string, [float, float, float, float]> = {};
// Options for the debug draw:
_debugDrawEnabled: boolean = false;
_debugDrawShowHiddenInstances: boolean = false;
_debugDrawShowPointsNames: boolean = false;
_debugDrawShowCustomPoints: boolean = false;
constructor() {
this._initialBehaviorSharedData = new Hashtable();
this._instances = new Hashtable();
this._instancesCache = new Hashtable();
this._objects = new Hashtable();
this._objectsCtor = new Hashtable();
this._layers = new Hashtable();
this._orderedLayers = [];
}
/**
* Return the time elapsed since the last frame,
* in milliseconds, for objects on the layer.
*/
abstract getElapsedTime(): float;
/**
* Get the renderer associated to the container.
*/
abstract getRenderer(): gdjs.RuntimeInstanceContainerRenderer;
/**
* Get the renderer for visual debugging associated to the container.
*/
abstract getDebuggerRenderer(): gdjs.DebuggerRenderer;
/**
* Get the {@link gdjs.RuntimeGame} associated to this.
*/
abstract getGame(): gdjs.RuntimeGame;
/**
* Get the {@link gdjs.RuntimeScene} associated to this.
*/
abstract getScene(): gdjs.RuntimeScene;
abstract getAsyncTasksManager(): gdjs.AsyncTasksManager;
/**
* Convert a point from the canvas coordinates (for example,
* the mouse position) to the container coordinates.
*
* @param x The x position, in container coordinates.
* @param y The y position, in container coordinates.
* @param result The point instance that is used to return the result.
*/
abstract convertCoords(x: float, y: float, result?: FloatPoint): FloatPoint;
/**
* Convert a point from the container coordinates (for example,
* an object position) to the canvas coordinates.
*
* @param sceneX The x position, in container coordinates.
* @param sceneY The y position, in container coordinates.
* @param result The point instance that is used to return the result.
*/
abstract convertInverseCoords(
sceneX: float,
sceneY: float,
result: FloatPoint
): FloatPoint;
/**
* @return the left bound of:
* - the game resolution for a {@link gdjs.RuntimeScene}
* - the default dimensions (the AABB of all its children) for a
* {@link gdjs.CustomRuntimeObject}.
*/
abstract getUnrotatedViewportMinX(): float;
/**
* @return the top bound of:
* - the game resolution for a {@link gdjs.RuntimeScene}
* - the default dimensions (the AABB of all its children) for a
* {@link gdjs.CustomRuntimeObject}.
*/
abstract getUnrotatedViewportMinY(): float;
/**
* @return the right bound of:
* - the game resolution for a {@link gdjs.RuntimeScene}
* - the default dimensions (the AABB of all its children) for a
* {@link gdjs.CustomRuntimeObject}.
*/
abstract getUnrotatedViewportMaxX(): float;
/**
* @return the bottom bound of:
* - the game resolution for a {@link gdjs.RuntimeScene}
* - the default dimensions (the AABB of all its children) for a
* {@link gdjs.CustomRuntimeObject}.
*/
abstract getUnrotatedViewportMaxY(): float;
/**
* @return the left bound of:
* - the initial game resolution for a {@link gdjs.RuntimeScene}
* - the initial default dimensions (inner area) set in the editor for a
* {@link gdjs.CustomRuntimeObject}.
*/
abstract getInitialUnrotatedViewportMinX(): float;
/**
* @return the top bound of:
* - the initial game resolution for a {@link gdjs.RuntimeScene}
* - the initial default dimensions (inner area) set in the editor for a
* {@link gdjs.CustomRuntimeObject}.
*/
abstract getInitialUnrotatedViewportMinY(): float;
/**
* @return the right bound of:
* - the initial game resolution for a {@link gdjs.RuntimeScene}
* - the initial default dimensions (inner area) set in the editor for a
* {@link gdjs.CustomRuntimeObject}.
*/
abstract getInitialUnrotatedViewportMaxX(): float;
/**
* @return the bottom bound of:
* - the initial game resolution for a {@link gdjs.RuntimeScene}
* - the initial default dimensions (inner area) set in the editor for a
* {@link gdjs.CustomRuntimeObject}.
*/
abstract getInitialUnrotatedViewportMaxY(): float;
/**
* @return the width of:
* - the game resolution for a {@link gdjs.RuntimeScene}
* - the default dimensions (the AABB of all its children) for a
* {@link gdjs.CustomRuntimeObject}.
*/
abstract getViewportWidth(): float;
/**
* @return the height of:
* - the game resolution for a {@link gdjs.RuntimeScene}
* - the default dimensions (the AABB of all its children) for a
* {@link gdjs.CustomRuntimeObject}.
*/
abstract getViewportHeight(): float;
/**
* @return the center X of:
* - the game resolution for a {@link gdjs.RuntimeScene}
* - the default dimensions (the AABB of all its children) for a
* {@link gdjs.CustomRuntimeObject}.
*/
abstract getViewportOriginX(): float;
/**
* @return the center Y of:
* - the game resolution for a {@link gdjs.RuntimeScene}
* - the default dimensions (the AABB of all its children) for a
* {@link gdjs.CustomRuntimeObject}.
*/
abstract getViewportOriginY(): float;
/**
* Triggered when the AABB of a child of the container could have changed.
*/
abstract onChildrenLocationChanged(): void;
/**
* Activate or deactivate the debug visualization for collisions and points.
*/
enableDebugDraw(
enableDebugDraw: boolean,
showHiddenInstances: boolean,
showPointsNames: boolean,
showCustomPoints: boolean
): void {
if (this._debugDrawEnabled && !enableDebugDraw) {
this.getDebuggerRenderer().clearDebugDraw();
}
this._debugDrawEnabled = enableDebugDraw;
this._debugDrawShowHiddenInstances = showHiddenInstances;
this._debugDrawShowPointsNames = showPointsNames;
this._debugDrawShowCustomPoints = showCustomPoints;
}
/**
* Check if an object is registered, meaning that instances of it can be
* created and lives in the container.
* @see gdjs.RuntimeInstanceContainer#registerObject
*/
isObjectRegistered(objectName: string): boolean {
return (
this._objects.containsKey(objectName) &&
this._instances.containsKey(objectName) &&
this._objectsCtor.containsKey(objectName)
);
}
/**
* Register a {@link gdjs.RuntimeObject} so that instances of it can be
* used in the container.
* @param objectData The data for the object to register.
*/
registerObject(objectData: ObjectData) {
this._objects.put(objectData.name, objectData);
this._instances.put(objectData.name, []);
// Cache the constructor
const Ctor = gdjs.getObjectConstructor(objectData.type);
this._objectsCtor.put(objectData.name, Ctor);
// Also prepare a cache for recycled instances, if the object supports it.
if (Ctor.supportsReinitialization) {
this._instancesCache.put(objectData.name, []);
}
}
/**
* Update the data of a {@link gdjs.RuntimeObject} so that instances use
* this when constructed.
* @param objectData The data for the object to register.
*/
updateObject(objectData: ObjectData): void {
if (!this.isObjectRegistered(objectData.name)) {
logger.warn(
'Tried to call updateObject for an object that was not registered (' +
objectData.name +
'). Call registerObject first.'
);
}
this._objects.put(objectData.name, objectData);
}
// Don't erase instances, nor instances cache, or objectsCtor cache.
/**
* Unregister a {@link gdjs.RuntimeObject}. Instances will be destroyed.
* @param objectName The name of the object to unregister.
*/
unregisterObject(objectName: string) {
const instances = this._instances.get(objectName);
if (instances) {
// This is sub-optimal: markObjectForDeletion will search the instance to
// remove in instances, so cost is O(n^2), n being the number of instances.
// As we're unregistering an object which only happen during a hot-reloading,
// this is fine.
const instancesToRemove = instances.slice();
for (let i = 0; i < instancesToRemove.length; i++) {
this.markObjectForDeletion(instancesToRemove[i]);
}
this._cacheOrClearRemovedInstances();
}
this._objects.remove(objectName);
this._instances.remove(objectName);
this._instancesCache.remove(objectName);
this._objectsCtor.remove(objectName);
}
/**
* Create objects from initial instances data (for example, the initial instances
* of the scene or the instances of an external layout).
*
* @param data The instances data
* @param xPos The offset on X axis
* @param yPos The offset on Y axis
* @param zPos The offset on Z axis
* @param trackByPersistentUuid If true, objects are tracked by setting their `persistentUuid`
* to the same as the associated instance. Useful for hot-reloading when instances are changed.
*/
createObjectsFrom(
data: InstanceData[],
xPos: float,
yPos: float,
zPos: float,
trackByPersistentUuid: boolean,
options?: {
excludedObjectNames?: Set<string> | null;
}
): void {
let zOffset: number = zPos;
let shouldTrackByPersistentUuid: boolean = trackByPersistentUuid;
if (arguments.length <= 4) {
/**
* Support for the previous signature (before 3D was introduced):
* createObjectsFrom(data, xPos, yPos, trackByPersistentUuid)
*/
zOffset = 0;
shouldTrackByPersistentUuid = arguments[3];
}
for (let i = 0, len = data.length; i < len; ++i) {
const instanceData = data[i];
const objectName = instanceData.name;
if (options?.excludedObjectNames?.has(objectName)) {
continue;
}
const newObject = this.createObject(objectName);
if (newObject !== null) {
if (shouldTrackByPersistentUuid) {
// Give the object the same persistentUuid as the instance, so that
// it can be hot-reloaded.
newObject.persistentUuid = instanceData.persistentUuid || null;
}
newObject.setPosition(instanceData.x + xPos, instanceData.y + yPos);
newObject.setAngle(instanceData.angle);
if (gdjs.Base3DHandler && gdjs.Base3DHandler.is3D(newObject)) {
newObject.setZ((instanceData.z || 0) + zOffset);
if (instanceData.rotationX !== undefined)
newObject.setRotationX(instanceData.rotationX);
if (instanceData.rotationY !== undefined)
newObject.setRotationY(instanceData.rotationY);
}
newObject.setZOrder(instanceData.zOrder);
newObject.setLayer(instanceData.layer);
newObject
.getVariables()
.initFrom(instanceData.initialVariables, true);
newObject.extraInitializationFromInitialInstance(instanceData);
}
}
}
/**
* Get the data representing the initial shared data of the scene for the specified behavior.
* @param name The name of the behavior
* @returns The shared data for the behavior, if any.
*/
getInitialSharedDataForBehavior(name: string): BehaviorSharedData | null {
return this._initialBehaviorSharedData.get(name);
}
/**
* Set the data representing the initial shared data of the scene for the specified behavior.
* @param name The name of the behavior
* @param sharedData The shared data for the behavior, or null to remove it.
*/
setInitialSharedDataForBehavior(
name: string,
sharedData: BehaviorSharedData | null
): void {
this._initialBehaviorSharedData.put(name, sharedData);
}
/**
* Set the default Z order for each layer, which is the highest Z order found on each layer.
* Useful as it ensures that instances created from events are, by default, shown in front
* of other instances.
*/
_setLayerDefaultZOrders() {
if (
this.getGame().getGameData().properties.useDeprecatedZeroAsDefaultZOrder
) {
// Deprecated option to still support games that were made considered 0 as the
// default Z order for all layers.
return;
}
const layerHighestZOrders: Record<string, number> = {};
const allInstances = this.getAdhocListOfAllInstances();
for (let i = 0, len = allInstances.length; i < len; ++i) {
const object = allInstances[i];
let layerName = object.getLayer();
const zOrder = object.getZOrder();
if (
layerHighestZOrders[layerName] === undefined ||
layerHighestZOrders[layerName] < zOrder
) {
layerHighestZOrders[layerName] = zOrder;
}
}
for (let layerName in layerHighestZOrders) {
this.getLayer(layerName).setDefaultZOrder(
layerHighestZOrders[layerName] + 1
);
}
}
_updateLayersCameraCoordinates(scale: float) {
this._layersCameraCoordinates = this._layersCameraCoordinates || {};
for (const name in this._layers.items) {
if (this._layers.items.hasOwnProperty(name)) {
const theLayer = this._layers.items[name];
this._layersCameraCoordinates[name] = this._layersCameraCoordinates[
name
] || [0, 0, 0, 0];
this._layersCameraCoordinates[name][0] =
theLayer.getCameraX() - (theLayer.getCameraWidth() / 2) * scale;
this._layersCameraCoordinates[name][1] =
theLayer.getCameraY() - (theLayer.getCameraHeight() / 2) * scale;
this._layersCameraCoordinates[name][2] =
theLayer.getCameraX() + (theLayer.getCameraWidth() / 2) * scale;
this._layersCameraCoordinates[name][3] =
theLayer.getCameraY() + (theLayer.getCameraHeight() / 2) * scale;
}
}
}
/**
* Called to update effects of layers before rendering.
*/
_updateLayersPreRender() {
for (const layer of this._orderedLayers) {
layer.updatePreRender(this);
}
}
/**
* Called to update visibility of the renderers of objects
* rendered on the scene ("culling"), update effects (of visible objects)
* and give a last chance for objects to update before rendering.
*
* Visibility is set to false if object is hidden, or if
* object is too far from the camera of its layer ("culling").
*/
_updateObjectsPreRender() {
const allInstancesList = this.getAdhocListOfAllInstances();
// TODO (3D) culling - add support for 3D object culling?
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
const object = allInstancesList[i];
const rendererObject = object.getRendererObject();
if (rendererObject) {
rendererObject.visible = !object.isHidden();
// Update effects, only for visible objects.
if (rendererObject.visible) {
this.getGame()
.getEffectsManager()
.updatePreRender(object.getRendererEffects(), object);
}
}
// Perform pre-render update.
object.updatePreRender(this);
}
return;
}
/**
* Empty the list of the removed objects:
*
* When an object is removed from the container, it is still kept in
* {@link gdjs.RuntimeInstanceContainer#_instancesRemoved}.
*
* This method should be called regularly (after events or behaviors steps) so as to clear this list
* and allows the removed objects to be cached (or destroyed if the cache is full).
*
* The removed objects could not be sent directly to the cache, as events may still be using them after
* removing them from the scene for example.
*/
_cacheOrClearRemovedInstances() {
for (let k = 0, lenk = this._instancesRemoved.length; k < lenk; ++k) {
const instance = this._instancesRemoved[k];
// Cache the instance to recycle it into a new instance later.
// If the object does not support recycling, the cache won't be defined.
const cache = this._instancesCache.get(instance.getName());
if (cache) {
if (cache.length < 128) {
cache.push(instance);
}
}
instance.onDestroyed();
}
this._instancesRemoved.length = 0;
}
/**
* Tool function filling _allInstancesList member with all the living object instances.
*/
private _constructListOfAllInstances() {
let currentListSize = 0;
for (const name in this._instances.items) {
if (this._instances.items.hasOwnProperty(name)) {
const list = this._instances.items[name];
const oldSize = currentListSize;
currentListSize += list.length;
for (let j = 0, lenj = list.length; j < lenj; ++j) {
if (oldSize + j < this._allInstancesList.length) {
this._allInstancesList[oldSize + j] = list[j];
} else {
this._allInstancesList.push(list[j]);
}
}
}
}
this._allInstancesList.length = currentListSize;
this._allInstancesListIsUpToDate = true;
}
/**
* @param objectName The name of the object
* @returns the instances of a given object in the container.
*/
getInstancesOf(objectName: string): gdjs.RuntimeObject[] {
return this._instances.items[objectName];
}
/**
* Get a list of all {@link gdjs.RuntimeObject} living in the container.
* You should not, normally, need this method at all. It's only to be used
* in exceptional use cases where you need to loop through all objects,
* and it won't be performant.
*
* @returns The list of all runtime objects in the container
*/
getAdhocListOfAllInstances(): gdjs.RuntimeObject[] {
if (!this._allInstancesListIsUpToDate) {
this._constructListOfAllInstances();
}
return this._allInstancesList;
}
/**
* Update the objects before launching the events.
*/
_updateObjectsPreEvents() {
// It is *mandatory* to create and iterate on a external list of all objects, as the behaviors
// may delete the objects.
const allInstancesList = this.getAdhocListOfAllInstances();
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
const obj = allInstancesList[i];
const elapsedTime = obj.getElapsedTime();
if (!obj.hasNoForces()) {
const averageForce = obj.getAverageForce();
const elapsedTimeInSeconds = elapsedTime / 1000;
obj.setX(obj.getX() + averageForce.getX() * elapsedTimeInSeconds);
obj.setY(obj.getY() + averageForce.getY() * elapsedTimeInSeconds);
obj.update(this);
obj.updateForces(elapsedTimeInSeconds);
} else {
obj.update(this);
}
obj.updateTimers(elapsedTime);
allInstancesList[i].stepBehaviorsPreEvents(this);
}
// Some behaviors may have request objects to be deleted.
this._cacheOrClearRemovedInstances();
}
/**
* Call each behavior stepPostEvents method.
*/
_stepBehaviorsPostEvents() {
this._cacheOrClearRemovedInstances();
// It is *mandatory* to create and iterate on a external list of all objects, as the behaviors
// may delete the objects.
const allInstancesList = this.getAdhocListOfAllInstances();
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
allInstancesList[i].stepBehaviorsPostEvents(this);
}
// Some behaviors may have request objects to be deleted.
this._cacheOrClearRemovedInstances();
}
/**
* Add an object to the instances living in the container.
* @param obj The object to be added.
*/
addObject(obj: gdjs.RuntimeObject) {
if (!this._instances.containsKey(obj.name)) {
this._instances.put(obj.name, []);
}
this._instances.get(obj.name).push(obj);
this._allInstancesListIsUpToDate = false;
}
/**
* Get all the instances of the object called name.
* @param name Name of the object for which the instances must be returned.
* @return The list of objects with the given name
*/
getObjects(name: string): gdjs.RuntimeObject[] {
if (!this._instances.containsKey(name)) {
logger.info(
'RuntimeScene.getObjects: No instances called "' +
name +
'"! Adding it.'
);
this._instances.put(name, []);
}
return this._instances.get(name);
}
/**
* Create a new object from its name. The object is also added to the instances
* living in the container (No need to call {@link addObject})
* @param objectName The name of the object to be created
* @return The created object
*/
createObject(objectName: string): gdjs.RuntimeObject | null {
if (
!this._objectsCtor.containsKey(objectName) ||
!this._objects.containsKey(objectName)
) {
// There is no such object in this container.
return null;
}
const objectData = this._objects.get(objectName);
// Create a new object using the object constructor (cached during loading)
// and the stored object's data:
const cache = this._instancesCache.get(objectName);
const ctor = this._objectsCtor.get(objectName);
let obj;
if (!cache || cache.length === 0) {
obj = new ctor(this, objectData);
} else {
// Reuse an objet destroyed before. If there is an object in the cache,
// then it means it does support reinitialization.
obj = cache.pop();
obj.reinitialize(objectData);
}
this.addObject(obj);
return obj;
}
/**
* Must be called whenever an object must be removed from the container.
* @param obj The object to be removed.
*/
markObjectForDeletion(obj: gdjs.RuntimeObject) {
// Add to the objects removed list.
// The objects will be sent to the instances cache or really deleted from memory later.
if (this._instancesRemoved.indexOf(obj) === -1) {
this._instancesRemoved.push(obj);
}
// Delete from the living instances.
if (this._instances.containsKey(obj.getName())) {
const objId = obj.id;
const allInstances = this._instances.get(obj.getName());
for (let i = 0, len = allInstances.length; i < len; ++i) {
if (allInstances[i].id == objId) {
allInstances.splice(i, 1);
this._allInstancesListIsUpToDate = false;
break;
}
}
}
// Notify the object it was removed from the container
obj.onDeletedFromScene();
// Notify the global callbacks
for (let j = 0; j < gdjs.callbacksObjectDeletedFromScene.length; ++j) {
gdjs.callbacksObjectDeletedFromScene[j](this, obj);
}
return;
}
/**
* Get the layer with the given name
* @param name The name of the layer
* @returns The layer, or the base layer if not found
*/
getLayer(name: string): gdjs.RuntimeLayer {
if (this._layers.containsKey(name)) {
return this._layers.get(name);
}
return this._layers.get('');
}
/**
* Check if a layer exists
* @param name The name of the layer
*/
hasLayer(name: string): boolean {
return this._layers.containsKey(name);
}
/**
* Add a layer.
* @param layerData The data to construct the layer
*/
abstract addLayer(layerData: LayerData);
/**
* Remove a layer. All {@link gdjs.RuntimeObject} on this layer will
* be moved back to the base layer.
* @param layerName The name of the layer to remove
*/
removeLayer(layerName: string) {
const existingLayer = this._layers.get(layerName);
if (!existingLayer) return;
const allInstances = this.getAdhocListOfAllInstances();
for (let i = 0; i < allInstances.length; ++i) {
const runtimeObject = allInstances[i];
if (runtimeObject.getLayer() === layerName) {
runtimeObject.setLayer('');
}
}
this._layers.remove(layerName);
const layerIndex = this._orderedLayers.indexOf(existingLayer);
this._orderedLayers.splice(layerIndex, 1);
}
/**
* Change the position of a layer.
*
* @param layerName The name of the layer to reorder
* @param newIndex The new position in the list of layers
*/
setLayerIndex(layerName: string, newIndex: integer): void {
const layer: gdjs.RuntimeLayer = this._layers.get(layerName);
if (!layer) {
return;
}
const layerIndex = this._orderedLayers.indexOf(layer);
if (layerIndex === newIndex) return;
this._orderedLayers.splice(layerIndex, 1);
this._orderedLayers.splice(newIndex, 0, layer);
this.getRenderer().setLayerIndex(layer, newIndex);
}
/**
* Fill the array passed as argument with the names of all layers
* @param result The array where to put the layer names
*/
getAllLayerNames(result: string[]) {
this._layers.keys(result);
}
/**
* Return the number of instances of the specified object living in the container.
* @param objectName The object name for which instances must be counted.
*/
getInstancesCountOnScene(objectName: string): integer {
const instances = this._instances.get(objectName);
if (instances) {
return instances.length;
}
return 0;
}
/**
* Update the objects positions according to their forces
*/
updateObjectsForces(): void {
for (const name in this._instances.items) {
if (this._instances.items.hasOwnProperty(name)) {
const list = this._instances.items[name];
for (let j = 0, listLen = list.length; j < listLen; ++j) {
const obj = list[j];
if (!obj.hasNoForces()) {
const averageForce = obj.getAverageForce();
const elapsedTimeInSeconds = obj.getElapsedTime() / 1000;
obj.setX(obj.getX() + averageForce.getX() * elapsedTimeInSeconds);
obj.setY(obj.getY() + averageForce.getY() * elapsedTimeInSeconds);
obj.updateForces(elapsedTimeInSeconds);
}
}
}
}
}
/**
* Clear any data structures to make sure memory is freed as soon as
* possible.
*/
_destroy() {
// It should not be necessary to reset these variables, but this help
// ensuring that all memory related to the container is released immediately.
this._layers = new Hashtable();
this._orderedLayers = [];
this._objects = new Hashtable();
this._instances = new Hashtable();
this._instancesCache = new Hashtable();
this._objectsCtor = new Hashtable();
this._allInstancesList = [];
this._instancesRemoved = [];
this._layersCameraCoordinates = {};
this._initialBehaviorSharedData = new Hashtable();
}
}
}