diff --git a/OpenSim/Region/Framework/Scenes/Scene.cs b/OpenSim/Region/Framework/Scenes/Scene.cs index 6323a88f8e..4fa4a7cded 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.cs @@ -3540,6 +3540,18 @@ namespace OpenSim.Region.Framework.Scenes } } + /// + /// Remove the given client from the scene. + /// + /// + /// Only clientstack code should call this directly. All other code should call IncomingCloseAgent() instead + /// to properly operate the state machine and avoid race conditions with other close requests (such as directly + /// from viewers). + /// + /// ID of agent to close + /// + /// Close the neighbour child agents associated with this client. + /// public override void RemoveClient(UUID agentID, bool closeChildAgents) { AgentCircuitData acd = m_authenticateHandler.GetAgentCircuitData(agentID); diff --git a/OpenSim/Region/Framework/Scenes/SceneBase.cs b/OpenSim/Region/Framework/Scenes/SceneBase.cs index 4eef162056..1dac676129 100644 --- a/OpenSim/Region/Framework/Scenes/SceneBase.cs +++ b/OpenSim/Region/Framework/Scenes/SceneBase.cs @@ -218,6 +218,19 @@ namespace OpenSim.Region.Framework.Scenes #region Add/Remove Agent/Avatar public abstract ISceneAgent AddNewClient(IClientAPI client, PresenceType type); + + /// + /// Remove the given client from the scene. + /// + /// + /// Only clientstack code should call this directly. All other code should call IncomingCloseAgent() instead + /// to properly operate the state machine and avoid race conditions with other close requests (such as directly + /// from viewers). + /// + /// ID of agent to close + /// + /// Close the neighbour child agents associated with this client. + /// public abstract void RemoveClient(UUID agentID, bool closeChildAgents); public bool TryGetScenePresence(UUID agentID, out object scenePresence) diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs b/OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs index d584782e74..502f85f02b 100644 --- a/OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs @@ -89,13 +89,6 @@ public sealed class BSCharacter : BSPhysObject // set _avatarVolume and _mass based on capsule size, _density and Scale ComputeAvatarVolumeAndMass(); - // The avatar's movement is controlled by this motor that speeds up and slows down - // the avatar seeking to reach the motor's target speed. - // This motor runs as a prestep action for the avatar so it will keep the avatar - // standing as well as moving. Destruction of the avatar will destroy the pre-step action. - m_moveActor = new BSActorAvatarMove(PhysScene, this, AvatarMoveActorName); - PhysicalActors.Add(AvatarMoveActorName, m_moveActor); - DetailLog("{0},BSCharacter.create,call,size={1},scale={2},density={3},volume={4},mass={5},pos={6}", LocalID, _size, Scale, Density, _avatarVolume, RawMass, pos); @@ -106,6 +99,13 @@ public sealed class BSCharacter : BSPhysObject // New body and shape into PhysBody and PhysShape PhysScene.Shapes.GetBodyAndShape(true, PhysScene.World, this); + // The avatar's movement is controlled by this motor that speeds up and slows down + // the avatar seeking to reach the motor's target speed. + // This motor runs as a prestep action for the avatar so it will keep the avatar + // standing as well as moving. Destruction of the avatar will destroy the pre-step action. + m_moveActor = new BSActorAvatarMove(PhysScene, this, AvatarMoveActorName); + PhysicalActors.Add(AvatarMoveActorName, m_moveActor); + SetPhysicalProperties(); }); return; diff --git a/OpenSim/Tools/pCampBot/Bot.cs b/OpenSim/Tools/pCampBot/Bot.cs index 9821180bc6..dac8ccb49b 100644 --- a/OpenSim/Tools/pCampBot/Bot.cs +++ b/OpenSim/Tools/pCampBot/Bot.cs @@ -63,11 +63,6 @@ namespace pCampBot /// public BotManager Manager { get; private set; } - /// - /// Bot config, passed from BotManager. - /// - private IConfig startupConfig; - /// /// Behaviours implemented by this bot. /// @@ -102,6 +97,8 @@ namespace pCampBot public string Name { get; private set; } public string Password { get; private set; } public string LoginUri { get; private set; } + public string StartLocation { get; private set; } + public string saveDir; public string wear; @@ -137,7 +134,7 @@ namespace pCampBot /// public Bot( BotManager bm, List behaviours, - string firstName, string lastName, string password, string loginUri) + string firstName, string lastName, string password, string startLocation, string loginUri) { ConnectionState = ConnectionState.Disconnected; @@ -151,11 +148,9 @@ namespace pCampBot Name = string.Format("{0} {1}", FirstName, LastName); Password = password; LoginUri = loginUri; + StartLocation = startLocation; Manager = bm; - startupConfig = bm.Config; - readconfig(); - Behaviours = behaviours; } @@ -163,7 +158,7 @@ namespace pCampBot //add additional steps and/or things the bot should do private void Action() { - while (true) + while (ConnectionState != ConnectionState.Disconnecting) lock (Behaviours) Behaviours.ForEach( b => @@ -176,14 +171,6 @@ namespace pCampBot ); } - /// - /// Read the Nini config and initialize - /// - public void readconfig() - { - wear = startupConfig.GetString("wear", "no"); - } - /// /// Tells LibSecondLife to logout and disconnect. Raises the disconnect events once it finishes. /// @@ -191,8 +178,8 @@ namespace pCampBot { ConnectionState = ConnectionState.Disconnecting; - if (m_actionThread != null) - m_actionThread.Abort(); +// if (m_actionThread != null) +// m_actionThread.Abort(); Client.Network.Logout(); } @@ -207,6 +194,7 @@ namespace pCampBot Client.Settings.AVATAR_TRACKING = false; Client.Settings.OBJECT_TRACKING = false; Client.Settings.SEND_AGENT_THROTTLE = true; + Client.Settings.SEND_AGENT_UPDATES = false; Client.Settings.SEND_PINGS = true; Client.Settings.STORE_LAND_PATCHES = false; Client.Settings.USE_ASSET_CACHE = false; @@ -224,7 +212,7 @@ namespace pCampBot ConnectionState = ConnectionState.Connecting; - if (Client.Network.Login(FirstName, LastName, Password, "pCampBot", "Your name")) + if (Client.Network.Login(FirstName, LastName, Password, "pCampBot", StartLocation, "Your name")) { ConnectionState = ConnectionState.Connected; @@ -481,9 +469,6 @@ namespace pCampBot public void Objects_NewPrim(object sender, PrimEventArgs args) { -// if (Name.EndsWith("4")) -// throw new Exception("Aaargh"); - Primitive prim = args.Prim; if (prim != null) diff --git a/OpenSim/Tools/pCampBot/BotManager.cs b/OpenSim/Tools/pCampBot/BotManager.cs index 74bd36a9a9..397a98e37c 100644 --- a/OpenSim/Tools/pCampBot/BotManager.cs +++ b/OpenSim/Tools/pCampBot/BotManager.cs @@ -51,6 +51,11 @@ namespace pCampBot public const int DefaultLoginDelay = 5000; + /// + /// True if pCampbot is in the process of shutting down. + /// + public bool ShuttingDown { get; private set; } + /// /// Delay between logins of multiple bots. /// @@ -62,6 +67,16 @@ namespace pCampBot /// protected CommandConsole m_console; + /// + /// Controls whether bots start out sending agent updates on connection. + /// + public bool InitBotSendAgentUpdates { get; set; } + + /// + /// Controls whether bots request textures for the object information they receive + /// + public bool InitBotRequestObjectTextures { get; set; } + /// /// Created bots, whether active or inactive. /// @@ -72,11 +87,6 @@ namespace pCampBot /// public Random Rng { get; private set; } - /// - /// Overall configuration. - /// - public IConfig Config { get; private set; } - /// /// Track the assets we have and have not received so we don't endlessly repeat requests. /// @@ -92,6 +102,9 @@ namespace pCampBot /// public BotManager() { + InitBotSendAgentUpdates = true; + InitBotRequestObjectTextures = true; + LoginDelay = DefaultLoginDelay; Rng = new Random(Environment.TickCount); @@ -148,31 +161,40 @@ namespace pCampBot /// /// How many bots to start up /// The configuration for the bots to use - public void dobotStartup(int botcount, IConfig cs) + public void dobotStartup(int botcount, IConfig startupConfig) { - Config = cs; + string firstName = startupConfig.GetString("firstname"); + string lastNameStem = startupConfig.GetString("lastname"); + string password = startupConfig.GetString("password"); + string loginUri = startupConfig.GetString("loginuri"); + string startLocation = startupConfig.GetString("start", "last"); + int fromBotNumber = startupConfig.GetInt("from", 0); + string wearSetting = startupConfig.GetString("wear", "no"); - string firstName = cs.GetString("firstname"); - string lastNameStem = cs.GetString("lastname"); - string password = cs.GetString("password"); - string loginUri = cs.GetString("loginuri"); + string startUri = ParseInputStartLocationToUri(startLocation); HashSet behaviourSwitches = new HashSet(); Array.ForEach( - cs.GetString("behaviours", "p").Split(new char[] { ',' }), b => behaviourSwitches.Add(b)); + startupConfig.GetString("behaviours", "p").Split(new char[] { ',' }), b => behaviourSwitches.Add(b)); MainConsole.Instance.OutputFormat( - "[BOT MANAGER]: Starting {0} bots connecting to {1}, named {2} {3}_", + "[BOT MANAGER]: Starting {0} bots connecting to {1}, location {2}, named {3} {4}_", botcount, loginUri, + startUri, firstName, lastNameStem); MainConsole.Instance.OutputFormat("[BOT MANAGER]: Delay between logins is {0}ms", LoginDelay); + MainConsole.Instance.OutputFormat("[BOT MANAGER]: BotsSendAgentUpdates is {0}", InitBotSendAgentUpdates); + MainConsole.Instance.OutputFormat("[BOT MANAGER]: InitBotRequestObjectTextures is {0}", InitBotRequestObjectTextures); for (int i = 0; i < botcount; i++) { - string lastName = string.Format("{0}_{1}", lastNameStem, i); + if (ShuttingDown) + break; + + string lastName = string.Format("{0}_{1}", lastNameStem, i + fromBotNumber); // We must give each bot its own list of instantiated behaviours since they store state. List behaviours = new List(); @@ -193,10 +215,49 @@ namespace pCampBot if (behaviourSwitches.Contains("t")) behaviours.Add(new TeleportBehaviour()); - StartBot(this, behaviours, firstName, lastName, password, loginUri); + StartBot(this, behaviours, firstName, lastName, password, loginUri, startUri, wearSetting); } } + /// + /// Parses the command line start location to a start string/uri that the login mechanism will recognize. + /// + /// + /// The input start location to URI. + /// + /// + /// Start location. + /// + private string ParseInputStartLocationToUri(string startLocation) + { + if (startLocation == "home" || startLocation == "last") + return startLocation; + + string regionName; + + // Just a region name or only one (!) extra component. Like a viewer, we will stick 128/128/0 on the end + Vector3 startPos = new Vector3(128, 128, 0); + + string[] startLocationComponents = startLocation.Split('/'); + + regionName = startLocationComponents[0]; + + if (startLocationComponents.Length >= 2) + { + float.TryParse(startLocationComponents[1], out startPos.X); + + if (startLocationComponents.Length >= 3) + { + float.TryParse(startLocationComponents[2], out startPos.Y); + + if (startLocationComponents.Length >= 4) + float.TryParse(startLocationComponents[3], out startPos.Z); + } + } + + return string.Format("uri:{0}&{1}&{2}&{3}", regionName, startPos.X, startPos.Y, startPos.Z); + } + // /// // /// Add additional bots (and threads) to our bot pool // /// @@ -226,15 +287,19 @@ namespace pCampBot /// Last name /// Password /// Login URI + /// Location to start the bot. Can be "last", "home" or a specific sim name. + /// public void StartBot( BotManager bm, List behaviours, - string firstName, string lastName, string password, string loginUri) + string firstName, string lastName, string password, string loginUri, string startLocation, string wearSetting) { MainConsole.Instance.OutputFormat( "[BOT MANAGER]: Starting bot {0} {1}, behaviours are {2}", firstName, lastName, string.Join(",", behaviours.ConvertAll(b => b.Name).ToArray())); - Bot pb = new Bot(bm, behaviours, firstName, lastName, password, loginUri); + Bot pb = new Bot(bm, behaviours, firstName, lastName, password, startLocation, loginUri); + pb.wear = wearSetting; + pb.Client.Settings.SEND_AGENT_UPDATES = InitBotSendAgentUpdates; pb.OnConnected += handlebotEvent; pb.OnDisconnected += handlebotEvent; @@ -306,7 +371,9 @@ namespace pCampBot private void HandleShutdown(string module, string[] cmd) { - m_log.Info("[BOTMANAGER]: Shutting down bots"); + MainConsole.Instance.Output("Shutting down"); + + ShuttingDown = true; doBotShutdown(); } diff --git a/OpenSim/Tools/pCampBot/pCampBot.cs b/OpenSim/Tools/pCampBot/pCampBot.cs index 2707a49f7d..b02f9170f9 100644 --- a/OpenSim/Tools/pCampBot/pCampBot.cs +++ b/OpenSim/Tools/pCampBot/pCampBot.cs @@ -26,6 +26,7 @@ */ using System; +using System.IO; using System.Reflection; using System.Threading; using log4net; @@ -50,28 +51,52 @@ namespace pCampBot { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + public const string ConfigFileName = "pCampbot.ini"; + [STAThread] public static void Main(string[] args) { XmlConfigurator.Configure(); - IConfig config = ParseConfig(args); - if (config.Get("help") != null || config.Get("loginuri") == null) + IConfig commandLineConfig = ParseConfig(args); + if (commandLineConfig.Get("help") != null || commandLineConfig.Get("loginuri") == null) { Help(); } - else if (config.Get("firstname") == null || config.Get("lastname") == null || config.Get("password") == null) + else if ( + commandLineConfig.Get("firstname") == null + || commandLineConfig.Get("lastname") == null + || commandLineConfig.Get("password") == null) { Console.WriteLine("ERROR: You must supply a firstname, lastname and password for the bots."); } else { - int botcount = config.GetInt("botcount", 1); - BotManager bm = new BotManager(); + string iniFilePath = Path.GetFullPath(Path.Combine(Util.configDir(), ConfigFileName)); + + if (File.Exists(iniFilePath)) + { + m_log.InfoFormat("[PCAMPBOT]: Reading configuration settings from {0}", iniFilePath); + + IConfigSource configSource = new IniConfigSource(iniFilePath); + + IConfig botConfig = configSource.Configs["Bot"]; + + if (botConfig != null) + { + bm.InitBotSendAgentUpdates + = botConfig.GetBoolean("SendAgentUpdates", bm.InitBotSendAgentUpdates); + bm.InitBotRequestObjectTextures + = botConfig.GetBoolean("RequestObjectTextures", bm.InitBotRequestObjectTextures); + } + } + + int botcount = commandLineConfig.GetInt("botcount", 1); + //startup specified number of bots. 1 is the default - Thread startBotThread = new Thread(o => bm.dobotStartup(botcount, config)); + Thread startBotThread = new Thread(o => bm.dobotStartup(botcount, commandLineConfig)); startBotThread.Name = "Initial start bots thread"; startBotThread.Start(); @@ -95,7 +120,9 @@ namespace pCampBot ArgvConfigSource cs = new ArgvConfigSource(args); cs.AddSwitch("Startup", "botcount", "n"); + cs.AddSwitch("Startup", "from", "f"); cs.AddSwitch("Startup", "loginuri", "l"); + cs.AddSwitch("Startup", "start", "s"); cs.AddSwitch("Startup", "firstname"); cs.AddSwitch("Startup", "lastname"); cs.AddSwitch("Startup", "password"); @@ -113,23 +140,26 @@ namespace pCampBot // You can either say no, to not load anything, yes, to load one of the default wearables, a folder // name, to load an specific folder, or save, to save an avatar with some already existing wearables // worn to the folder MyAppearance/FirstName_LastName, and the load it. + Console.WriteLine( - "usage: pCampBot <-loginuri loginuri> [OPTIONS]\n" + - "Spawns a set of bots to test an OpenSim region\n\n" + - " -l, -loginuri loginuri for sim to log into (required)\n" + - " -n, -botcount number of bots to start (default: 1)\n" + - " -firstname first name for the bots\n" + - " -lastname lastname for the bots. Each lastname will have _ appended, e.g. Ima Bot_0\n" + - " -password password for the bots\n" + - " -b, behaviours behaviours for bots. Comma separated, e.g. p,g. Default is p\n" + - " current options are:\n" + - " p (physics - bots constantly move and jump around)\n" + - " g (grab - bots randomly click prims whether set clickable or not)\n" + - " n (none - bots do nothing)\n" + - " t (teleport - bots regularly teleport between regions on the grid)\n" + + "usage: pCampBot <-loginuri loginuri> [OPTIONS]\n" + + "Spawns a set of bots to test an OpenSim region\n\n" + + " -l, -loginuri loginuri for grid/standalone (required)\n" + + " -s, -start optional start location for bots. Can be \"last\", \"home\" or a specific location with or without co-ords (e.g. \"region1\" or \"region2/50/30/90\"\n" + + " -firstname first name for the bots\n" + + " -lastname lastname for the bots. Each lastname will have _ appended, e.g. Ima Bot_0\n" + + " -password password for the bots\n" + + " -n, -botcount optional number of bots to start (default: 1)\n" + + " -f, -from optional starting number for login bot names, e.g. 25 will login Ima Bot_25, Ima Bot_26, etc. (default: 0)" + + " -b, behaviours behaviours for bots. Comma separated, e.g. p,g. Default is p\n" + + " current options are:\n" + + " p (physics - bots constantly move and jump around)\n" + + " g (grab - bots randomly click prims whether set clickable or not)\n" + + " n (none - bots do nothing)\n" + + " t (teleport - bots regularly teleport between regions on the grid)\n" // " c (cross)" + - " -wear set appearance folder to load from (default: no)\n" + - " -h, -help show this message"); + + " -wear optional folder from which to load appearance data, \"no\" if there is no such folder (default: no)\n" + + " -h, -help show this message.\n"); } } } diff --git a/bin/pCampbot.ini.example b/bin/pCampbot.ini.example new file mode 100644 index 0000000000..f44feaee1f --- /dev/null +++ b/bin/pCampbot.ini.example @@ -0,0 +1,15 @@ +; This is the example config file for pCampbot +; To use it, copy this file to pCampbot.ini and change settings if required + +[Bot] + ; Control whether bots should regularly send agent updates + ; Not doing this will reduce CPU requirements for pCampbot but greatly + ; reduce the realism compared to viewers which are constantly sending AgentUpdates UDP packets. + ; Defaults to true. + SendAgentUpdates = true + + ; Control whether bots will requests textures when receiving object information + ; Not doing this will reduce CPU requirements for pCampbot but greatly + ; reduce the realism compared to viewers which requests such texture data if not already cached. + ; Defaults to true. + RequestObjectTextures = true