diff options
Diffstat (limited to 'OpenSim/Region/ScriptEngine/XEngine')
-rw-r--r-- | OpenSim/Region/ScriptEngine/XEngine/Tests/XEngineTest.cs | 2 | ||||
-rw-r--r-- | OpenSim/Region/ScriptEngine/XEngine/XEngine.cs | 142 |
2 files changed, 115 insertions, 29 deletions
diff --git a/OpenSim/Region/ScriptEngine/XEngine/Tests/XEngineTest.cs b/OpenSim/Region/ScriptEngine/XEngine/Tests/XEngineTest.cs index f331658..5abfe9a 100644 --- a/OpenSim/Region/ScriptEngine/XEngine/Tests/XEngineTest.cs +++ b/OpenSim/Region/ScriptEngine/XEngine/Tests/XEngineTest.cs | |||
@@ -44,7 +44,7 @@ namespace OpenSim.Region.ScriptEngine.XEngine.Tests | |||
44 | /// XEngine tests. | 44 | /// XEngine tests. |
45 | /// </summary> | 45 | /// </summary> |
46 | [TestFixture] | 46 | [TestFixture] |
47 | public class XEngineTest | 47 | public class XEngineTest : OpenSimTestCase |
48 | { | 48 | { |
49 | private TestScene m_scene; | 49 | private TestScene m_scene; |
50 | private XEngine m_xEngine; | 50 | private XEngine m_xEngine; |
diff --git a/OpenSim/Region/ScriptEngine/XEngine/XEngine.cs b/OpenSim/Region/ScriptEngine/XEngine/XEngine.cs index 9f05666..05dd7ab 100644 --- a/OpenSim/Region/ScriptEngine/XEngine/XEngine.cs +++ b/OpenSim/Region/ScriptEngine/XEngine/XEngine.cs | |||
@@ -31,6 +31,7 @@ using System.Collections.Generic; | |||
31 | using System.Diagnostics; //for [DebuggerNonUserCode] | 31 | using System.Diagnostics; //for [DebuggerNonUserCode] |
32 | using System.Globalization; | 32 | using System.Globalization; |
33 | using System.IO; | 33 | using System.IO; |
34 | using System.Linq; | ||
34 | using System.Reflection; | 35 | using System.Reflection; |
35 | using System.Security; | 36 | using System.Security; |
36 | using System.Security.Policy; | 37 | using System.Security.Policy; |
@@ -107,6 +108,24 @@ namespace OpenSim.Region.ScriptEngine.XEngine | |||
107 | private IXmlRpcRouter m_XmlRpcRouter; | 108 | private IXmlRpcRouter m_XmlRpcRouter; |
108 | private int m_EventLimit; | 109 | private int m_EventLimit; |
109 | private bool m_KillTimedOutScripts; | 110 | private bool m_KillTimedOutScripts; |
111 | |||
112 | /// <summary> | ||
113 | /// Number of milliseconds we will wait for a script event to complete on script stop before we forcibly abort | ||
114 | /// its thread. | ||
115 | /// </summary> | ||
116 | /// <remarks> | ||
117 | /// It appears that if a script thread is aborted whilst it is holding ReaderWriterLockSlim (possibly the write | ||
118 | /// lock) then the lock is not properly released. This causes mono 2.6, 2.10 and possibly | ||
119 | /// later to crash, sometimes with symptoms such as a leap to 100% script usage and a vm thead dump showing | ||
120 | /// all threads waiting on release of ReaderWriterLockSlim write thread which none of the threads listed | ||
121 | /// actually hold. | ||
122 | /// | ||
123 | /// Pausing for event completion reduces the risk of this happening. However, it may be that aborting threads | ||
124 | /// is not a mono issue per se but rather a risky activity in itself in an AppDomain that is not immediately | ||
125 | /// shutting down. | ||
126 | /// </remarks> | ||
127 | private int m_WaitForEventCompletionOnScriptStop = 1000; | ||
128 | |||
110 | private string m_ScriptEnginesPath = null; | 129 | private string m_ScriptEnginesPath = null; |
111 | 130 | ||
112 | private ExpiringCache<UUID, bool> m_runFlags = new ExpiringCache<UUID, bool>(); | 131 | private ExpiringCache<UUID, bool> m_runFlags = new ExpiringCache<UUID, bool>(); |
@@ -316,6 +335,9 @@ namespace OpenSim.Region.ScriptEngine.XEngine | |||
316 | m_EventLimit = m_ScriptConfig.GetInt("EventLimit", 30); | 335 | m_EventLimit = m_ScriptConfig.GetInt("EventLimit", 30); |
317 | m_KillTimedOutScripts = m_ScriptConfig.GetBoolean("KillTimedOutScripts", false); | 336 | m_KillTimedOutScripts = m_ScriptConfig.GetBoolean("KillTimedOutScripts", false); |
318 | m_SaveTime = m_ScriptConfig.GetInt("SaveInterval", 120) * 1000; | 337 | m_SaveTime = m_ScriptConfig.GetInt("SaveInterval", 120) * 1000; |
338 | m_WaitForEventCompletionOnScriptStop | ||
339 | = m_ScriptConfig.GetInt("WaitForEventCompletionOnScriptStop", m_WaitForEventCompletionOnScriptStop); | ||
340 | |||
319 | m_ScriptEnginesPath = m_ScriptConfig.GetString("ScriptEnginesPath", "ScriptEngines"); | 341 | m_ScriptEnginesPath = m_ScriptConfig.GetString("ScriptEnginesPath", "ScriptEngines"); |
320 | 342 | ||
321 | m_Prio = ThreadPriority.BelowNormal; | 343 | m_Prio = ThreadPriority.BelowNormal; |
@@ -371,7 +393,7 @@ namespace OpenSim.Region.ScriptEngine.XEngine | |||
371 | 393 | ||
372 | MainConsole.Instance.Commands.AddCommand( | 394 | MainConsole.Instance.Commands.AddCommand( |
373 | "Scripts", false, "scripts show", "scripts show [<script-item-uuid>]", "Show script information", | 395 | "Scripts", false, "scripts show", "scripts show [<script-item-uuid>]", "Show script information", |
374 | "Show information on all scripts known to the script engine." | 396 | "Show information on all scripts known to the script engine.\n" |
375 | + "If a <script-item-uuid> is given then only information on that script will be shown.", | 397 | + "If a <script-item-uuid> is given then only information on that script will be shown.", |
376 | HandleShowScripts); | 398 | HandleShowScripts); |
377 | 399 | ||
@@ -390,22 +412,30 @@ namespace OpenSim.Region.ScriptEngine.XEngine | |||
390 | MainConsole.Instance.Commands.AddCommand( | 412 | MainConsole.Instance.Commands.AddCommand( |
391 | "Scripts", false, "scripts resume", "scripts resume [<script-item-uuid>]", "Resumes all suspended scripts", | 413 | "Scripts", false, "scripts resume", "scripts resume [<script-item-uuid>]", "Resumes all suspended scripts", |
392 | "Resumes all currently suspended scripts.\n" | 414 | "Resumes all currently suspended scripts.\n" |
393 | + "Resumed scripts will process all events accumulated whilst suspended." | 415 | + "Resumed scripts will process all events accumulated whilst suspended.\n" |
394 | + "If a <script-item-uuid> is given then only that script will be resumed. Otherwise, all suitable scripts are resumed.", | 416 | + "If a <script-item-uuid> is given then only that script will be resumed. Otherwise, all suitable scripts are resumed.", |
395 | (module, cmdparams) => HandleScriptsAction(cmdparams, HandleResumeScript)); | 417 | (module, cmdparams) => HandleScriptsAction(cmdparams, HandleResumeScript)); |
396 | 418 | ||
397 | MainConsole.Instance.Commands.AddCommand( | 419 | MainConsole.Instance.Commands.AddCommand( |
398 | "Scripts", false, "scripts stop", "scripts stop [<script-item-uuid>]", "Stops all running scripts", | 420 | "Scripts", false, "scripts stop", "scripts stop [<script-item-uuid>]", "Stops all running scripts", |
399 | "Stops all running scripts." | 421 | "Stops all running scripts.\n" |
400 | + "If a <script-item-uuid> is given then only that script will be stopped. Otherwise, all suitable scripts are stopped.", | 422 | + "If a <script-item-uuid> is given then only that script will be stopped. Otherwise, all suitable scripts are stopped.", |
401 | (module, cmdparams) => HandleScriptsAction(cmdparams, HandleStopScript)); | 423 | (module, cmdparams) => HandleScriptsAction(cmdparams, HandleStopScript)); |
402 | 424 | ||
403 | MainConsole.Instance.Commands.AddCommand( | 425 | MainConsole.Instance.Commands.AddCommand( |
404 | "Scripts", false, "scripts start", "scripts start [<script-item-uuid>]", "Starts all stopped scripts", | 426 | "Scripts", false, "scripts start", "scripts start [<script-item-uuid>]", "Starts all stopped scripts", |
405 | "Starts all stopped scripts." | 427 | "Starts all stopped scripts.\n" |
406 | + "If a <script-item-uuid> is given then only that script will be started. Otherwise, all suitable scripts are started.", | 428 | + "If a <script-item-uuid> is given then only that script will be started. Otherwise, all suitable scripts are started.", |
407 | (module, cmdparams) => HandleScriptsAction(cmdparams, HandleStartScript)); | 429 | (module, cmdparams) => HandleScriptsAction(cmdparams, HandleStartScript)); |
408 | 430 | ||
431 | MainConsole.Instance.Commands.AddCommand( | ||
432 | "Scripts", false, "debug script log", "debug scripts log <item-id> <log-level>", "Extra debug logging for a script", | ||
433 | "Activates or deactivates extra debug logging for the given script.\n" | ||
434 | + "Level == 0, deactivate extra debug logging.\n" | ||
435 | + "Level >= 1, log state changes.\n" | ||
436 | + "Level >= 2, log event invocations.\n", | ||
437 | HandleDebugScriptLogCommand); | ||
438 | |||
409 | // MainConsole.Instance.Commands.AddCommand( | 439 | // MainConsole.Instance.Commands.AddCommand( |
410 | // "Debug", false, "debug xengine", "debug xengine [<level>]", | 440 | // "Debug", false, "debug xengine", "debug xengine [<level>]", |
411 | // "Turn on detailed xengine debugging.", | 441 | // "Turn on detailed xengine debugging.", |
@@ -414,6 +444,41 @@ namespace OpenSim.Region.ScriptEngine.XEngine | |||
414 | // HandleDebugLevelCommand); | 444 | // HandleDebugLevelCommand); |
415 | } | 445 | } |
416 | 446 | ||
447 | private void HandleDebugScriptLogCommand(string module, string[] args) | ||
448 | { | ||
449 | if (!(MainConsole.Instance.ConsoleScene == null || MainConsole.Instance.ConsoleScene == m_Scene)) | ||
450 | return; | ||
451 | |||
452 | if (args.Length != 5) | ||
453 | { | ||
454 | MainConsole.Instance.Output("Usage: debug script log <item-id> <log-level>"); | ||
455 | return; | ||
456 | } | ||
457 | |||
458 | UUID itemId; | ||
459 | |||
460 | if (!ConsoleUtil.TryParseConsoleUuid(MainConsole.Instance, args[3], out itemId)) | ||
461 | return; | ||
462 | |||
463 | int newLevel; | ||
464 | |||
465 | if (!ConsoleUtil.TryParseConsoleInt(MainConsole.Instance, args[4], out newLevel)) | ||
466 | return; | ||
467 | |||
468 | IScriptInstance si; | ||
469 | |||
470 | lock (m_Scripts) | ||
471 | { | ||
472 | // XXX: We can't give the user feedback on a bad item id because this may apply to a different script | ||
473 | // engine | ||
474 | if (!m_Scripts.TryGetValue(itemId, out si)) | ||
475 | return; | ||
476 | } | ||
477 | |||
478 | si.DebugLevel = newLevel; | ||
479 | MainConsole.Instance.OutputFormat("Set debug level of {0} {1} to {2}", si.ScriptName, si.ItemID, newLevel); | ||
480 | } | ||
481 | |||
417 | /// <summary> | 482 | /// <summary> |
418 | /// Change debug level | 483 | /// Change debug level |
419 | /// </summary> | 484 | /// </summary> |
@@ -445,9 +510,21 @@ namespace OpenSim.Region.ScriptEngine.XEngine | |||
445 | /// </summary> | 510 | /// </summary> |
446 | /// <param name="cmdparams"></param> | 511 | /// <param name="cmdparams"></param> |
447 | /// <param name="instance"></param> | 512 | /// <param name="instance"></param> |
448 | /// <returns>true if we're okay to proceed, false if not.</returns> | 513 | /// <param name="comparer">Basis on which to sort output. Can be null if no sort needs to take place</param> |
449 | private void HandleScriptsAction(string[] cmdparams, Action<IScriptInstance> action) | 514 | private void HandleScriptsAction(string[] cmdparams, Action<IScriptInstance> action) |
450 | { | 515 | { |
516 | HandleScriptsAction<object>(cmdparams, action, null); | ||
517 | } | ||
518 | |||
519 | /// <summary> | ||
520 | /// Parse the raw item id into a script instance from the command params if it's present. | ||
521 | /// </summary> | ||
522 | /// <param name="cmdparams"></param> | ||
523 | /// <param name="instance"></param> | ||
524 | /// <param name="keySelector">Basis on which to sort output. Can be null if no sort needs to take place</param> | ||
525 | private void HandleScriptsAction<TKey>( | ||
526 | string[] cmdparams, Action<IScriptInstance> action, Func<IScriptInstance, TKey> keySelector) | ||
527 | { | ||
451 | if (!(MainConsole.Instance.ConsoleScene == null || MainConsole.Instance.ConsoleScene == m_Scene)) | 528 | if (!(MainConsole.Instance.ConsoleScene == null || MainConsole.Instance.ConsoleScene == m_Scene)) |
452 | return; | 529 | return; |
453 | 530 | ||
@@ -458,7 +535,12 @@ namespace OpenSim.Region.ScriptEngine.XEngine | |||
458 | 535 | ||
459 | if (cmdparams.Length == 2) | 536 | if (cmdparams.Length == 2) |
460 | { | 537 | { |
461 | foreach (IScriptInstance instance in m_Scripts.Values) | 538 | IEnumerable<IScriptInstance> scripts = m_Scripts.Values; |
539 | |||
540 | if (keySelector != null) | ||
541 | scripts = scripts.OrderBy<IScriptInstance, TKey>(keySelector); | ||
542 | |||
543 | foreach (IScriptInstance instance in scripts) | ||
462 | action(instance); | 544 | action(instance); |
463 | 545 | ||
464 | return; | 546 | return; |
@@ -468,7 +550,7 @@ namespace OpenSim.Region.ScriptEngine.XEngine | |||
468 | 550 | ||
469 | if (!UUID.TryParse(rawItemId, out itemId)) | 551 | if (!UUID.TryParse(rawItemId, out itemId)) |
470 | { | 552 | { |
471 | MainConsole.Instance.OutputFormat("Error - {0} is not a valid UUID", rawItemId); | 553 | MainConsole.Instance.OutputFormat("ERROR: {0} is not a valid UUID", rawItemId); |
472 | return; | 554 | return; |
473 | } | 555 | } |
474 | 556 | ||
@@ -505,9 +587,20 @@ namespace OpenSim.Region.ScriptEngine.XEngine | |||
505 | StringBuilder sb = new StringBuilder(); | 587 | StringBuilder sb = new StringBuilder(); |
506 | sb.AppendFormat("Status of XEngine instance for {0}\n", m_Scene.RegionInfo.RegionName); | 588 | sb.AppendFormat("Status of XEngine instance for {0}\n", m_Scene.RegionInfo.RegionName); |
507 | 589 | ||
590 | long scriptsLoaded, eventsQueued = 0, eventsProcessed = 0; | ||
591 | |||
508 | lock (m_Scripts) | 592 | lock (m_Scripts) |
509 | sb.AppendFormat("Scripts loaded : {0}\n", m_Scripts.Count); | 593 | { |
594 | scriptsLoaded = m_Scripts.Count; | ||
595 | |||
596 | foreach (IScriptInstance si in m_Scripts.Values) | ||
597 | { | ||
598 | eventsQueued += si.EventsQueued; | ||
599 | eventsProcessed += si.EventsProcessed; | ||
600 | } | ||
601 | } | ||
510 | 602 | ||
603 | sb.AppendFormat("Scripts loaded : {0}\n", scriptsLoaded); | ||
511 | sb.AppendFormat("Unique scripts : {0}\n", m_uniqueScripts.Count); | 604 | sb.AppendFormat("Unique scripts : {0}\n", m_uniqueScripts.Count); |
512 | sb.AppendFormat("Scripts waiting for load : {0}\n", m_CompileQueue.Count); | 605 | sb.AppendFormat("Scripts waiting for load : {0}\n", m_CompileQueue.Count); |
513 | sb.AppendFormat("Max threads : {0}\n", m_ThreadPool.MaxThreads); | 606 | sb.AppendFormat("Max threads : {0}\n", m_ThreadPool.MaxThreads); |
@@ -516,6 +609,8 @@ namespace OpenSim.Region.ScriptEngine.XEngine | |||
516 | sb.AppendFormat("In use threads : {0}\n", m_ThreadPool.InUseThreads); | 609 | sb.AppendFormat("In use threads : {0}\n", m_ThreadPool.InUseThreads); |
517 | sb.AppendFormat("Work items waiting : {0}\n", m_ThreadPool.WaitingCallbacks); | 610 | sb.AppendFormat("Work items waiting : {0}\n", m_ThreadPool.WaitingCallbacks); |
518 | // sb.AppendFormat("Assemblies loaded : {0}\n", m_Assemblies.Count); | 611 | // sb.AppendFormat("Assemblies loaded : {0}\n", m_Assemblies.Count); |
612 | sb.AppendFormat("Events queued : {0}\n", eventsQueued); | ||
613 | sb.AppendFormat("Events processed : {0}\n", eventsProcessed); | ||
519 | 614 | ||
520 | SensorRepeat sr = AsyncCommandManager.GetSensorRepeatPlugin(this); | 615 | SensorRepeat sr = AsyncCommandManager.GetSensorRepeatPlugin(this); |
521 | sb.AppendFormat("Sensors : {0}\n", sr != null ? sr.SensorsCount : 0); | 616 | sb.AppendFormat("Sensors : {0}\n", sr != null ? sr.SensorsCount : 0); |
@@ -546,7 +641,7 @@ namespace OpenSim.Region.ScriptEngine.XEngine | |||
546 | } | 641 | } |
547 | } | 642 | } |
548 | 643 | ||
549 | HandleScriptsAction(cmdparams, HandleShowScript); | 644 | HandleScriptsAction<long>(cmdparams, HandleShowScript, si => si.EventsProcessed); |
550 | } | 645 | } |
551 | 646 | ||
552 | private void HandleShowScript(IScriptInstance instance) | 647 | private void HandleShowScript(IScriptInstance instance) |
@@ -576,11 +671,10 @@ namespace OpenSim.Region.ScriptEngine.XEngine | |||
576 | 671 | ||
577 | sb.AppendFormat("Script name : {0}\n", instance.ScriptName); | 672 | sb.AppendFormat("Script name : {0}\n", instance.ScriptName); |
578 | sb.AppendFormat("Status : {0}\n", status); | 673 | sb.AppendFormat("Status : {0}\n", status); |
579 | 674 | sb.AppendFormat("Queued events : {0}\n", instance.EventsQueued); | |
580 | lock (eq) | 675 | sb.AppendFormat("Processed events : {0}\n", instance.EventsProcessed); |
581 | sb.AppendFormat("Queued events : {0}\n", eq.Count); | ||
582 | |||
583 | sb.AppendFormat("Item UUID : {0}\n", instance.ItemID); | 676 | sb.AppendFormat("Item UUID : {0}\n", instance.ItemID); |
677 | sb.AppendFormat("Asset UUID : {0}\n", instance.AssetID); | ||
584 | sb.AppendFormat("Containing part name: {0}\n", instance.PrimName); | 678 | sb.AppendFormat("Containing part name: {0}\n", instance.PrimName); |
585 | sb.AppendFormat("Containing part UUID: {0}\n", instance.ObjectID); | 679 | sb.AppendFormat("Containing part UUID: {0}\n", instance.ObjectID); |
586 | sb.AppendFormat("Position : {0}\n", sop.AbsolutePosition); | 680 | sb.AppendFormat("Position : {0}\n", sop.AbsolutePosition); |
@@ -1089,8 +1183,7 @@ namespace OpenSim.Region.ScriptEngine.XEngine | |||
1089 | 1183 | ||
1090 | string assembly = ""; | 1184 | string assembly = ""; |
1091 | 1185 | ||
1092 | CultureInfo USCulture = new CultureInfo("en-US"); | 1186 | Culture.SetCurrentCulture(); |
1093 | Thread.CurrentThread.CurrentCulture = USCulture; | ||
1094 | 1187 | ||
1095 | Dictionary<KeyValuePair<int, int>, KeyValuePair<int, int>> linemap; | 1188 | Dictionary<KeyValuePair<int, int>, KeyValuePair<int, int>> linemap; |
1096 | 1189 | ||
@@ -1347,9 +1440,7 @@ namespace OpenSim.Region.ScriptEngine.XEngine | |||
1347 | lockScriptsForWrite(false); | 1440 | lockScriptsForWrite(false); |
1348 | instance.ClearQueue(); | 1441 | instance.ClearQueue(); |
1349 | 1442 | ||
1350 | // Give the script some time to finish processing its last event. Simply aborting the script thread can | 1443 | instance.Stop(m_WaitForEventCompletionOnScriptStop); |
1351 | // cause issues on mono 2.6, 2.10 and possibly later where locks are not released properly on abort. | ||
1352 | instance.Stop(1000); | ||
1353 | 1444 | ||
1354 | // bool objectRemoved = false; | 1445 | // bool objectRemoved = false; |
1355 | 1446 | ||
@@ -1485,6 +1576,7 @@ namespace OpenSim.Region.ScriptEngine.XEngine | |||
1485 | startInfo.StartSuspended = true; | 1576 | startInfo.StartSuspended = true; |
1486 | 1577 | ||
1487 | m_ThreadPool = new SmartThreadPool(startInfo); | 1578 | m_ThreadPool = new SmartThreadPool(startInfo); |
1579 | m_ThreadPool.Name = "XEngine"; | ||
1488 | } | 1580 | } |
1489 | 1581 | ||
1490 | // | 1582 | // |
@@ -1504,8 +1596,7 @@ namespace OpenSim.Region.ScriptEngine.XEngine | |||
1504 | /// <returns></returns> | 1596 | /// <returns></returns> |
1505 | private object ProcessEventHandler(object parms) | 1597 | private object ProcessEventHandler(object parms) |
1506 | { | 1598 | { |
1507 | CultureInfo USCulture = new CultureInfo("en-US"); | 1599 | Culture.SetCurrentCulture(); |
1508 | Thread.CurrentThread.CurrentCulture = USCulture; | ||
1509 | 1600 | ||
1510 | IScriptInstance instance = (ScriptInstance) parms; | 1601 | IScriptInstance instance = (ScriptInstance) parms; |
1511 | 1602 | ||
@@ -1693,7 +1784,7 @@ namespace OpenSim.Region.ScriptEngine.XEngine | |||
1693 | { | 1784 | { |
1694 | IScriptInstance instance = GetInstance(itemID); | 1785 | IScriptInstance instance = GetInstance(itemID); |
1695 | if (instance != null) | 1786 | if (instance != null) |
1696 | instance.ResetScript(); | 1787 | instance.ResetScript(m_WaitForEventCompletionOnScriptStop); |
1697 | } | 1788 | } |
1698 | 1789 | ||
1699 | public void StartScript(UUID itemID) | 1790 | public void StartScript(UUID itemID) |
@@ -1708,16 +1799,11 @@ namespace OpenSim.Region.ScriptEngine.XEngine | |||
1708 | public void StopScript(UUID itemID) | 1799 | public void StopScript(UUID itemID) |
1709 | { | 1800 | { |
1710 | IScriptInstance instance = GetInstance(itemID); | 1801 | IScriptInstance instance = GetInstance(itemID); |
1802 | |||
1711 | if (instance != null) | 1803 | if (instance != null) |
1712 | { | 1804 | instance.Stop(m_WaitForEventCompletionOnScriptStop); |
1713 | // Give the script some time to finish processing its last event. Simply aborting the script thread can | ||
1714 | // cause issues on mono 2.6, 2.10 and possibly later where locks are not released properly on abort. | ||
1715 | instance.Stop(1000); | ||
1716 | } | ||
1717 | else | 1805 | else |
1718 | { | ||
1719 | m_runFlags.AddOrUpdate(itemID, false, 240); | 1806 | m_runFlags.AddOrUpdate(itemID, false, 240); |
1720 | } | ||
1721 | } | 1807 | } |
1722 | 1808 | ||
1723 | public DetectParams GetDetectParams(UUID itemID, int idx) | 1809 | public DetectParams GetDetectParams(UUID itemID, int idx) |