public void Initialize() { if (Initialized == true) { return; } //Use invariant culture Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture; //Set up the logger string logPath = Path.Combine(LoggingConstants.LogFolderPath, LoggingConstants.LOG_FILE_NAME); if (Utilities.FileHelpers.ValidatePathForFile(logPath) == false) { Console.WriteLine("Logger path cannot be validated. This is a problem! Double check the path is correct."); } //Set up the logger //Cap the size at 10 MB TRBotLogger.SetupLogger(logPath, Serilog.Events.LogEventLevel.Verbose, Serilog.RollingInterval.Day, 1024L * 1024L * 10L, TimeSpan.FromSeconds(60d)); //Initialize database string databasePath = Path.Combine(DataConstants.DataFolderPath, DataConstants.DATABASE_FILE_NAME); TRBotLogger.Logger.Information($"Validating database at: {databasePath}"); if (Utilities.FileHelpers.ValidatePathForFile(databasePath) == false) { TRBotLogger.Logger.Error($"Cannot create database path at {databasePath}. Check if you have permission to write to this directory. Aborting."); return; } TRBotLogger.Logger.Information("Database path validated! Initializing database and importing migrations."); DatabaseManager.SetDatabasePath(databasePath); DatabaseManager.InitAndMigrateContext(); TRBotLogger.Logger.Information("Checking to initialize default values for missing database entries."); //Check for and initialize default values if the database was newly created or needs updating int addedDefaultEntries = DataHelper.InitDefaultData(); if (addedDefaultEntries > 0) { TRBotLogger.Logger.Information($"Added {addedDefaultEntries} additional entries to the database."); } //Set the logger's log level long logLevel = DataHelper.GetSettingInt(SettingsConstants.LOG_LEVEL, (long)Serilog.Events.LogEventLevel.Information); TRBotLogger.SetLogLevel((Serilog.Events.LogEventLevel)logLevel); TRBotLogger.Logger.Information("Initializing client service"); //Initialize client service InitClientService(); //If the client service doesn't exist, we can't continue if (ClientService == null) { TRBotLogger.Logger.Error("Client service failed to initialize; please check your settings. Aborting."); return; } //Set client service and message cooldown MsgHandler.SetClientService(ClientService); MessageThrottlingOptions msgThrottleOption = (MessageThrottlingOptions)DataHelper.GetSettingInt(SettingsConstants.MESSAGE_THROTTLE_TYPE, 0L); long msgCooldown = DataHelper.GetSettingInt(SettingsConstants.MESSAGE_COOLDOWN, 30000L); long msgThrottleCount = DataHelper.GetSettingInt(SettingsConstants.MESSAGE_THROTTLE_COUNT, 20L); MsgHandler.SetMessageThrottling(msgThrottleOption, new MessageThrottleData(msgCooldown, msgThrottleCount)); //Subscribe to events UnsubscribeEvents(); SubscribeEvents(); DataContainer.SetMessageHandler(MsgHandler); DataContainer.SetDataReloader(DataReloader); TRBotLogger.Logger.Information("Setting up virtual controller manager."); VirtualControllerTypes lastVControllerType = (VirtualControllerTypes)DataHelper.GetSettingInt(SettingsConstants.LAST_VCONTROLLER_TYPE, 0L); VirtualControllerTypes curVControllerType = VControllerHelper.ValidateVirtualControllerType(lastVControllerType, TRBotOSPlatform.CurrentOS); //Show a message saying the previous value wasn't supported and save the changes if (VControllerHelper.IsVControllerSupported(lastVControllerType, TRBotOSPlatform.CurrentOS) == false) { MsgHandler.QueueMessage($"Current virtual controller {lastVControllerType} is not supported by the {TRBotOSPlatform.CurrentOS} platform. Switched it to the default of {curVControllerType} for this platform."); using (BotDBContext context = DatabaseManager.OpenContext()) { Settings lastVControllerSetting = DataHelper.GetSettingNoOpen(SettingsConstants.LAST_VCONTROLLER_TYPE, context); lastVControllerSetting.ValueInt = (long)curVControllerType; context.SaveChanges(); } } DataContainer.SetCurVControllerType(curVControllerType); IVirtualControllerManager controllerMngr = VControllerHelper.GetVControllerMngrForType(curVControllerType); DataContainer.SetControllerManager(controllerMngr); int controllerCount = 0; //Clamp the controller count to the min and max allowed by the virtual controller manager using (BotDBContext context = DatabaseManager.OpenContext()) { Settings joystickCountSetting = DataHelper.GetSettingNoOpen(SettingsConstants.JOYSTICK_COUNT, context); int minCount = DataContainer.ControllerMngr.MinControllers; int maxCount = DataContainer.ControllerMngr.MaxControllers; //Validate controller count if (joystickCountSetting.ValueInt < minCount) { MsgHandler.QueueMessage($"Controller count of {joystickCountSetting.ValueInt} in database is invalid. Clamping to the min of {minCount}."); joystickCountSetting.ValueInt = minCount; context.SaveChanges(); } else if (joystickCountSetting.ValueInt > maxCount) { MsgHandler.QueueMessage($"Controller count of {joystickCountSetting.ValueInt} in database is invalid. Clamping to the max of {maxCount}."); joystickCountSetting.ValueInt = maxCount; context.SaveChanges(); } controllerCount = (int)joystickCountSetting.ValueInt; } DataContainer.ControllerMngr.Initialize(); int acquiredCount = DataContainer.ControllerMngr.InitControllers(controllerCount); TRBotLogger.Logger.Information($"Setting up virtual controller {curVControllerType} and acquired {acquiredCount} controllers!"); CmdHandler = new CommandHandler(); CmdHandler.Initialize(DataContainer, RoutineHandler); DataReloader.SoftDataReloadedEvent -= OnSoftReload; DataReloader.SoftDataReloadedEvent += OnSoftReload; DataReloader.HardDataReloadedEvent -= OnHardReload; DataReloader.HardDataReloadedEvent += OnHardReload; //Initialize routines InitRoutines(); //Cache our parser InputParser = new Parser(); Initialized = true; }
public override void ExecuteCommand(EvtChatCommandArgs args) { List <string> arguments = args.Command.ArgumentsAsList; long lastVControllerType = DataHelper.GetSettingInt(SettingsConstants.LAST_VCONTROLLER_TYPE, 0L); VirtualControllerTypes curVCType = (VirtualControllerTypes)lastVControllerType; //See the virtual controller if (arguments.Count == 0) { QueueMessage($"The current virtual controller is {(VirtualControllerTypes)lastVControllerType}. To set the virtual controller, add one as an argument: {CachedVCTypesStr}"); return; } //Invalid number of arguments if (arguments.Count > 1) { QueueMessage(UsageMessage); return; } using (BotDBContext context = DatabaseManager.OpenContext()) { //Check if the user has the ability to set the type User user = DataHelper.GetUserNoOpen(args.Command.ChatMessage.Username, context); if (user != null && user.HasEnabledAbility(PermissionConstants.SET_VCONTROLLER_TYPE_ABILITY) == false) { QueueMessage("You don't have the ability to set the virtual controller type!"); return; } } string vControllerStr = arguments[0]; //Parse if (EnumUtility.TryParseEnumValue(vControllerStr, out VirtualControllerTypes parsedVCType) == false) { QueueMessage($"Please enter a valid virtual controller: {CachedVCTypesStr}"); return; } //Same type if (parsedVCType == curVCType && DataContainer.ControllerMngr.Initialized == true) { QueueMessage($"The current virtual controller is already {curVCType}!"); return; } //Make sure this virtual controller is supported on this platform if (VControllerHelper.IsVControllerSupported(parsedVCType, TRBotOSPlatform.CurrentOS) == false) { QueueMessage($"{parsedVCType} virtual controllers are not supported on {TRBotOSPlatform.CurrentOS} platforms."); return; } using (BotDBContext context = DatabaseManager.OpenContext()) { //Set the value and save Settings lastVControllerSetting = DataHelper.GetSettingNoOpen(SettingsConstants.LAST_VCONTROLLER_TYPE, context); lastVControllerSetting.ValueInt = (long)parsedVCType; context.SaveChanges(); } //Stop and halt all inputs InputHandler.StopAndHaltAllInputs(); try { //Assign the new controller manager IVirtualControllerManager controllerMngr = VControllerHelper.GetVControllerMngrForType(parsedVCType); if (controllerMngr == null) { QueueMessage($"Virtual controller manager of new type {parsedVCType} failed to initialize. This indicates an invalid {SettingsConstants.LAST_VCONTROLLER_TYPE} setting in the database or an unimplemented platform."); return; } //Dispose the controller manager DataContainer.ControllerMngr.Dispose(); DataContainer.SetCurVControllerType(parsedVCType); DataContainer.SetControllerManager(controllerMngr); DataContainer.ControllerMngr.Initialize(); //Ensure we clamp the controller count to the correct value for this virtual controller manager int minControllerCount = DataContainer.ControllerMngr.MinControllers; int maxControllerCount = DataContainer.ControllerMngr.MaxControllers; int joystickCount = (int)DataHelper.GetSettingInt(SettingsConstants.JOYSTICK_COUNT, 0L); int newJoystickCount = joystickCount; if (joystickCount < minControllerCount) { QueueMessage($"Controller count of {joystickCount} is invalid. Clamping to the min of {minControllerCount}."); newJoystickCount = minControllerCount; } else if (joystickCount > maxControllerCount) { QueueMessage($"Controller count of {joystickCount} is invalid. Clamping to the max of {maxControllerCount}."); newJoystickCount = maxControllerCount; } if (joystickCount != newJoystickCount) { using (BotDBContext context = DatabaseManager.OpenContext()) { //Adjust the joystick count setting Settings joystickCountSetting = DataHelper.GetSettingNoOpen(SettingsConstants.JOYSTICK_COUNT, context); joystickCountSetting.ValueInt = newJoystickCount; context.SaveChanges(); } } int acquiredCount = DataContainer.ControllerMngr.InitControllers(newJoystickCount); QueueMessage($"Set virtual controller to {parsedVCType} with {acquiredCount} controller(s) and reset all running inputs!"); } catch (Exception e) { DataContainer.MessageHandler.QueueMessage($"Error changing virtual controller type: {e.Message}"); return; } finally { //Resume inputs InputHandler.ResumeRunningInputs(); } }
public void SetControllerManager(IVirtualControllerManager controllerMngr) { ControllerMngr = controllerMngr; }
private static void ExecuteInput(object obj) { /************************************************************* * PERFORMANCE CRITICAL CODE * * Even the smallest change must be thoroughly tested * *************************************************************/ //Increment running threads Interlocked.Increment(ref RunningInputThreads); //Get the input list - this should have been validated beforehand InputWrapper inputWrapper = (InputWrapper)obj; Parser.Input[][] inputArray = inputWrapper.InputArray; Stopwatch sw = new Stopwatch(); List <int> indices = new List <int>(16); IVirtualControllerManager vcMngr = InputGlobals.ControllerMngr; int controllerCount = vcMngr.ControllerCount; int[] nonWaits = new int[controllerCount]; //This is used to track which controller ports were used across all inputs //This helps prevent updating controllers that weren't used at the end int[] usedControllerPorts = new int[controllerCount]; ConsoleBase curConsole = InputGlobals.CurrentConsole; //Don't check for overflow to improve performance unchecked { for (int i = 0; i < inputArray.Length; i++) { ref Parser.Input[] inputs = ref inputArray[i]; indices.Clear(); //Press all buttons unless it's a release input for (int j = 0; j < inputs.Length; j++) { indices.Add(j); //Get a reference to avoid copying the struct ref Parser.Input input = ref inputs[j]; //Don't do anything for a wait input if (curConsole.IsWait(input) == true) { continue; } int port = input.controllerPort; //Get the controller we're using IVirtualController controller = vcMngr.GetController(port); //These are set to 1 instead of incremented to prevent any chance of overflow nonWaits[port] = 1; usedControllerPorts[port] = 1; if (input.release == true) { controller.ReleaseInput(input); } else { controller.PressInput(input); } } //Update the controllers if there are non-wait inputs for (int waitIdx = 0; waitIdx < nonWaits.Length; waitIdx++) { if (nonWaits[waitIdx] > 0) { IVirtualController controller = vcMngr.GetController(waitIdx); controller.UpdateController(); nonWaits[waitIdx] = 0; } } sw.Start(); while (indices.Count > 0) { //End the input prematurely if (StopRunningInputs == true) { goto End; } //Release buttons when we should for (int j = indices.Count - 1; j >= 0; j--) { ref Parser.Input input = ref inputs[indices[j]]; if (sw.ElapsedMilliseconds < input.duration) { continue; } //Release if the input isn't a hold and isn't a wait input if (input.hold == false && curConsole.IsWait(input) == false) { int port = input.controllerPort; //Get the controller we're using IVirtualController controller = vcMngr.GetController(port); controller.ReleaseInput(input); //Track that we have a non-wait or hold input so we can update the controller with all input releases at once nonWaits[port] = 1; usedControllerPorts[port] = 1; } indices.RemoveAt(j); } //Update the controllers if there are non-wait inputs for (int waitIdx = 0; waitIdx < nonWaits.Length; waitIdx++) { if (nonWaits[waitIdx] > 0) { IVirtualController controller = vcMngr.GetController(waitIdx); controller.UpdateController(); nonWaits[waitIdx] = 0; } } }
public InputWrapper(ParsedInput[][] inputArray, GameConsole console, IVirtualControllerManager vcMngr) { InputArray = inputArray; Console = console; VCManager = vcMngr; }
private static void ExecuteInput(object obj) { /************************************************************* * PERFORMANCE CRITICAL CODE * * Even the smallest change must be thoroughly tested * *************************************************************/ //Increment running threads Interlocked.Increment(ref RunningInputThreads); //Get the input list - this should have been validated beforehand InputWrapper inputWrapper = (InputWrapper)obj; ParsedInput[][] inputArray = inputWrapper.InputArray; Stopwatch sw = new Stopwatch(); List <int> indices = new List <int>(16); IVirtualControllerManager vcMngr = inputWrapper.VCManager; int controllerCount = vcMngr.ControllerCount; //Use Span with stack memory to avoid allocations and improve speed Span <int> nonWaits = stackalloc int[controllerCount]; nonWaits.Clear(); //This is used to track which controller ports were used across all inputs //This helps prevent updating controllers that weren't used at the end Span <int> usedControllerPorts = stackalloc int[controllerCount]; usedControllerPorts.Clear(); GameConsole curConsole = inputWrapper.Console; //Don't check for overflow to improve performance unchecked { for (int i = 0; i < inputArray.Length; i++) { ref ParsedInput[] inputs = ref inputArray[i]; indices.Clear(); //Press all buttons unless it's a release input for (int j = 0; j < inputs.Length; j++) { indices.Add(j); //Get a reference to avoid copying the struct ref ParsedInput input = ref inputs[j]; //Don't do anything for a blank input if (curConsole.IsBlankInput(input) == true) { continue; } int port = input.controllerPort; //Get the controller we're using IVirtualController controller = vcMngr.GetController(port); //These are set to 1 instead of incremented to prevent any chance of overflow nonWaits[port] = 1; usedControllerPorts[port] = 1; if (input.release == true) { InputHelper.ReleaseInput(input, curConsole, controller); } else { InputHelper.PressInput(input, curConsole, controller); } } //Update the controllers if there are non-wait inputs for (int waitIdx = 0; waitIdx < nonWaits.Length; waitIdx++) { //Store by ref and change directly to avoid calling the indexer twice ref int nonWaitVal = ref nonWaits[waitIdx]; if (nonWaitVal > 0) { IVirtualController controller = vcMngr.GetController(waitIdx); controller.UpdateController(); nonWaitVal = 0; } }
/// <summary> /// Carries out a set of inputs. /// </summary> /// <param name="inputList">A list of lists of inputs to execute.</param> public static void CarryOutInput(List <List <ParsedInput> > inputList, GameConsole currentConsole, IVirtualControllerManager vcManager) { /*Kimimaru: We're using a thread pool for efficiency * Though very unlikely, there's a chance the input won't execute right away if it has to wait for a thread to be available * However, there are often plenty of available threads, so this shouldn't be an issue since we use only one thread per input string * Uncomment the following lines to see how many threads are supported in the pool on your machine */ //ThreadPool.GetMinThreads(out int workermin, out int completionmin); //ThreadPool.GetMaxThreads(out int workerthreads, out int completionPortThreads); //TRBotLogger.Logger.Information($"Min workers: {workermin} Max workers: {workerthreads} Min async IO threads: {completionmin} Max async IO threads: {completionPortThreads}"); //Kimimaru: Copy the input list over to an array, which is more performant //and lets us bypass redundant copying and bounds checks in certain instances //This matters once we've begun processing inputs since we're //trying to reduce the delay between pressing and releasing inputs as much as we can ParsedInput[][] inputArray = new ParsedInput[inputList.Count][]; for (int i = 0; i < inputArray.Length; i++) { inputArray[i] = inputList[i].ToArray(); } InputWrapper inputWrapper = new InputWrapper(inputArray, currentConsole, vcManager); ThreadPool.QueueUserWorkItem(new WaitCallback(ExecuteInput), inputWrapper); }