/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the OpenSimulator Project nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.Reflection; using System.Threading; using OpenMetaverse; using log4net; using Nini.Config; using OpenSim.Framework; using OpenSim.Framework.Console; using OpenSim.Region.Framework.Interfaces; using GridRegion = OpenSim.Services.Interfaces.GridRegion; namespace OpenSim.Region.Framework.Scenes { public abstract class SceneBase : IScene { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); #pragma warning disable 414 private static readonly string LogHeader = "[SCENE]"; #pragma warning restore 414 #region Events public event restart OnRestart; #endregion #region Fields public string Name { get { return RegionInfo.RegionName; } } public IConfigSource Config { get { return GetConfig(); } } protected virtual IConfigSource GetConfig() { return null; } /// /// All the region modules attached to this scene. /// public Dictionary RegionModules { get { return m_regionModules; } } private Dictionary m_regionModules = new Dictionary(); /// /// The module interfaces available from this scene. /// protected Dictionary> ModuleInterfaces = new Dictionary>(); /// /// These two objects hold the information about any formats used /// by modules that hold agent specific data. /// protected List FormatsOffered = new List(); protected Dictionary> FormatsWanted = new Dictionary>(); protected Dictionary ModuleAPIMethods = new Dictionary(); /// /// The module commanders available from this scene /// protected Dictionary m_moduleCommanders = new Dictionary(); /// /// Registered classes that are capable of creating entities. /// protected Dictionary m_entityCreators = new Dictionary(); /// /// The last allocated local prim id. When a new local id is requested, the next number in the sequence is /// dispensed. /// protected int m_lastAllocatedLocalId = 720000; protected int m_lastAllocatedIntId = 7200; protected readonly ClientManager m_clientManager = new ClientManager(); public bool LoginsEnabled { get { return m_loginsEnabled; } set { if (m_loginsEnabled != value) { m_loginsEnabled = value; EventManager.TriggerRegionLoginsStatusChange(this); } } } private bool m_loginsEnabled; public bool Ready { get { return m_ready; } set { if (m_ready != value) { m_ready = value; EventManager.TriggerRegionReadyStatusChange(this); } } } private bool m_ready; public float TimeDilation { get { return 1.0f; } } public ITerrainChannel Heightmap; public ITerrainChannel Bakedmap; /// /// Allows retrieval of land information for this scene. /// public ILandChannel LandChannel; /// /// Manage events that occur in this scene (avatar movement, script rez, etc.). Commonly used by region modules /// to subscribe to scene events. /// public EventManager EventManager { get { return m_eventManager; } } protected EventManager m_eventManager; protected ScenePermissions m_permissions; public ScenePermissions Permissions { get { return m_permissions; } } /* Used by the loadbalancer plugin on GForge */ protected RegionStatus m_regStatus; public RegionStatus RegionStatus { get { return m_regStatus; } set { m_regStatus = value; } } #endregion public SceneBase(RegionInfo regInfo) { RegionInfo = regInfo; } #region Update Methods /// /// Called to update the scene loop by a number of frames and until shutdown. /// /// /// Number of frames to update. Exits on shutdown even if there are frames remaining. /// If -1 then updates until shutdown. /// /// true if update completed within minimum frame time, false otherwise. public abstract void Update(int frames); #endregion #region Terrain Methods /// /// Loads the World heightmap /// public abstract void LoadWorldMap(); /// /// Send the region heightmap to the client /// /// Client to send to public virtual void SendLayerData(IClientAPI RemoteClient) { // RemoteClient.SendLayerData(Heightmap.GetFloatsSerialised()); ITerrainModule terrModule = RequestModuleInterface(); terrModule?.PushTerrain(RemoteClient); } #endregion #region Add/Remove Agent/Avatar public abstract ISceneAgent AddNewAgent(IClientAPI client, PresenceType type); public abstract bool CloseAgent(UUID agentID, bool force); public bool TryGetScenePresence(UUID agentID, out object scenePresence) { if (TryGetScenePresence(agentID, out ScenePresence sp)) { scenePresence = sp; return true; } scenePresence = null; return false; } /// /// Try to get a scene presence from the scene /// /// /// null if there is no scene presence with the given agent id /// true if there was a scene presence with the given id, false otherwise. public abstract bool TryGetScenePresence(UUID agentID, out ScenePresence scenePresence); #endregion /// /// /// /// public virtual RegionInfo RegionInfo { get; private set; } #region admin stuff public abstract void OtherRegionUp(GridRegion otherRegion); public virtual string GetSimulatorVersion() { return "OpenSimulator Server"; } #endregion #region Shutdown /// /// Tidy before shutdown /// public virtual void Close() { try { EventManager.TriggerShutdown(); } catch (Exception e) { m_log.Error(string.Format("[SCENE]: SceneBase.cs: Close() - Failed with exception {0}", e)); } } #endregion /// /// Returns a new unallocated local ID /// /// A brand new local ID public uint AllocateLocalId() { return (uint)Interlocked.Increment(ref m_lastAllocatedLocalId); } public int AllocateIntId() { return Interlocked.Increment(ref m_lastAllocatedLocalId); } #region Module Methods /// /// Add a region-module to this scene. TODO: This will replace AddModule in the future. /// /// /// public void AddRegionModule(string name, IRegionModuleBase module) { if (!RegionModules.ContainsKey(name)) { RegionModules.Add(name, module); } } public void RemoveRegionModule(string name) { RegionModules.Remove(name); } /// /// Register a module commander. /// /// public void RegisterModuleCommander(ICommander commander) { lock (m_moduleCommanders) { m_moduleCommanders.Add(commander.Name, commander); } } /// /// Unregister a module commander and all its commands /// /// public void UnregisterModuleCommander(string name) { lock (m_moduleCommanders) { ICommander commander; if (m_moduleCommanders.TryGetValue(name, out commander)) m_moduleCommanders.Remove(name); } } /// /// Get a module commander /// /// /// The module commander, null if no module commander with that name was found public ICommander GetCommander(string name) { lock (m_moduleCommanders) { if (m_moduleCommanders.ContainsKey(name)) return m_moduleCommanders[name]; } return null; } public Dictionary GetCommanders() { return m_moduleCommanders; } public List GetFormatsOffered() { List ret = new List(FormatsOffered); return ret; } protected void CheckAndAddAgentDataFormats(object mod) { if (!(mod is IAgentStatefulModule)) return; IAgentStatefulModule m = (IAgentStatefulModule)mod; List renderFormats = m.GetRenderStateFormats(); List acceptFormats = m.GetAcceptStateFormats(); foreach (UUID render in renderFormats) { if (!(FormatsOffered.Contains(render))) FormatsOffered.Add(render); } if (acceptFormats.Count == 0) return; if (FormatsWanted.ContainsKey(mod)) return; FormatsWanted[mod] = acceptFormats; } /// /// Register an interface to a region module. This allows module methods to be called directly as /// well as via events. If there is already a module registered for this interface, it is not replaced /// (is this the best behaviour?) /// /// public void RegisterModuleInterface(M mod) { // m_log.DebugFormat("[SCENE BASE]: Registering interface {0}", typeof(M)); List l = null; if (!ModuleInterfaces.TryGetValue(typeof(M), out l)) { l = new List(); ModuleInterfaces.Add(typeof(M), l); } if (l.Count > 0) return; l.Add(mod); CheckAndAddAgentDataFormats(mod); if (mod is IEntityCreator) { IEntityCreator entityCreator = (IEntityCreator)mod; foreach (PCode pcode in entityCreator.CreationCapabilities) { m_entityCreators[pcode] = entityCreator; } } } public void UnregisterModuleInterface(M mod) { // We can't unregister agent stateful modules because // that would require much more data to be held about formats // and would make that code slower and less efficient. // No known modules are unregistered anyway, ever, unless // the simulator shuts down anyway. if (mod is IAgentStatefulModule) return; List l; if (ModuleInterfaces.TryGetValue(typeof(M), out l)) { if (l.Remove(mod)) { if (mod is IEntityCreator) { IEntityCreator entityCreator = (IEntityCreator)mod; foreach (PCode pcode in entityCreator.CreationCapabilities) { m_entityCreators[pcode] = null; } } } } } public void StackModuleInterface(M mod) { List l; if (ModuleInterfaces.ContainsKey(typeof(M))) l = ModuleInterfaces[typeof(M)]; else l = new List(); if (l.Contains(mod)) return; l.Add(mod); CheckAndAddAgentDataFormats(mod); if (mod is IEntityCreator) { IEntityCreator entityCreator = (IEntityCreator)mod; foreach (PCode pcode in entityCreator.CreationCapabilities) { m_entityCreators[pcode] = entityCreator; } } ModuleInterfaces[typeof(M)] = l; } /// /// For the given interface, retrieve the region module which implements it. /// /// null if there is no registered module implementing that interface public T RequestModuleInterface() { if (ModuleInterfaces.TryGetValue(typeof(T), out List mio ) && mio.Count > 0) return (T)mio[0]; return default; } /// /// For the given interface, retrieve an array of region modules that implement it. /// /// an empty array if there are no registered modules implementing that interface public T[] RequestModuleInterfaces() { if (ModuleInterfaces.ContainsKey(typeof(T))) { List ret = new List(); foreach (Object o in ModuleInterfaces[typeof(T)]) ret.Add((T)o); return ret.ToArray(); } else { return new T[] {}; } } #endregion /// /// Call this from a region module to add a command to the OpenSim console. /// /// /// /// /// /// public void AddCommand(IRegionModuleBase module, string command, string shorthelp, string longhelp, CommandDelegate callback) { AddCommand(module, command, shorthelp, longhelp, string.Empty, callback); } /// /// Call this from a region module to add a command to the OpenSim console. /// /// /// The use of IRegionModuleBase is a cheap trick to get a different method signature, /// though all new modules should be using interfaces descended from IRegionModuleBase anyway. /// /// /// Category of the command. This is the section under which it will appear when the user asks for help /// /// /// /// /// public void AddCommand( string category, IRegionModuleBase module, string command, string shorthelp, string longhelp, CommandDelegate callback) { AddCommand(category, module, command, shorthelp, longhelp, string.Empty, callback); } /// /// Call this from a region module to add a command to the OpenSim console. /// /// /// /// /// /// /// public void AddCommand(IRegionModuleBase module, string command, string shorthelp, string longhelp, string descriptivehelp, CommandDelegate callback) { string moduleName = (module is null) ? module.Name : string.Empty; AddCommand(moduleName, module, command, shorthelp, longhelp, descriptivehelp, callback); } /// /// Call this from a region module to add a command to the OpenSim console. /// /// /// Category of the command. This is the section under which it will appear when the user asks for help /// /// /// /// /// /// /// public void AddCommand( string category, IRegionModuleBase module, string command, string shorthelp, string longhelp, string descriptivehelp, CommandDelegate callback) { if (MainConsole.Instance is null) return; bool shared = module is not null && module is ISharedRegionModule; MainConsole.Instance.Commands.AddCommand( category, shared, command, shorthelp, longhelp, descriptivehelp, callback); } public virtual ISceneObject DeserializeObject(string representation) { return null; } public virtual bool AllowScriptCrossings { get { return false; } } public virtual void Start() { } public void Restart() { OnRestart?.Invoke(RegionInfo); } public abstract bool CheckClient(UUID agentID, System.Net.IPEndPoint ep); } }