• Visit Rebornbuddy
  • Big Thread of Upcoming 3.0 Changes!

    Discussion in 'Archives' started by pushedx, Mar 28, 2017.

    Thread Status:
    Not open for further replies.
    1. pushedx

      pushedx Moderator Moderator Buddy Core Dev

      Joined:
      Sep 24, 2013
      Messages:
      4,252
      Likes Received:
      290
      Trophy Points:
      83
      A lot of changes are upcoming for the 3.0 expansion. This thread will start to go over some changes planned that are being tested now, and will most likely take effect in their full capacity along with the reasoning behind the changes. Make sure to watch this thread to get notifications when it updates!
       
    2. pushedx

      pushedx Moderator Moderator Buddy Core Dev

      Joined:
      Sep 24, 2013
      Messages:
      4,252
      Likes Received:
      290
      Trophy Points:
      83
      ILogic.Execute Changes

      ILogic.Execute was added in May of 2015. It's purpose was to replace the IConfigurable.SetConfiguration / IConfigurable.GetConfiguration functions which were used to allow cross-content communication for sending and receiving data.

      The problem with SetConfiguration and GetConfiguration were that they were tied to working with settings in general, and there was no way to perform a similar operation other than calling IRunnable.Logic (which is now currently ILogic.Logic). However, that was undesirable because Logic returned a bool and was a task.

      The idea behind ILogic.Execute was to provide a non-task way to send and receive data, as well as execute arbitrary logic. It accomplished this goal, but the results have not been as expected when it comes to making use of it. Execute returns an object, and uses an implied mechanic of inferring "null" means not handled. While this is not a problem with how limited the function is used, it does present a design problem that needed to be solved.

      The current replacement for ILogic.Execute is IMessageHandler.Message.
      Code:
          /// <summary>
          /// An interface for an object that can handle dynamic messages.
          /// </summary>
          public interface IMessageHandler
          {
              /// <summary>
              /// Implements logic to handle a message passed through the system.
              /// </summary>
              /// <param name="message">The message to be processed.</param>
              /// <returns>A MessageResult that describes the result..</returns>
              MessageResult Message(Message message);
          }
      
      The MessageResult enum has two states:
      Code:
          /// <summary>
          /// An enum to represent message results.
          /// </summary>
          public enum MessageResult
          {
              /// <summary>The message was processed.</summary>
              Processed,
      
              /// <summary>The message was not processed.</summary>
              Unprocessed,
          }
      
      The Message class itself is still under construction, but contains a string Id, a list of "inputs", and a list of "outputs".

      The idea behind the new Message function is as follows:
      - Logic can explicitly return if the message was processed or not. This can allow a "capabilities" system to be developed.
      - A list of results means multiple return values can now be supported to match the dynamic nature of messages in general.
      - The name and purpose is more intuitive as a whole.

      Utility.BroadcastEventExecute is currently used to invoke Execute on the current bot, the current routine, and all enabled plugins. With these changes, the function is now BroadcastMessage, which returns the new Message object that was processed by all content. This allows callers to have access to the results of the message handling finally, as before the results were discarded.

      Now for examples of how code changes will look. Please remember some stuff is still under construction, so final syntax might change in favor of offering more convenience.

      Current AutoLogin plugin code:
      Code:
              /// <summary>
              /// Non-coroutine logic to execute.
              /// </summary>
              /// <param name="name">The name of the logic to invoke.</param>
              /// <param name="param">The data passed to the logic.</param>
              /// <returns>Data from the executed logic.</returns>
              public object Execute(string name, params dynamic[] param)
              {
                  // Support other code setting the time to next login with.
                  if (name == "SetNextLoginTime")
                  {
                      AutoLoginSettings.Instance.NextLoginTime = (DateTime) param[0];
                      Log.InfoFormat("[AutoLogin] Execute({0}) = {1}.", name, AutoLoginSettings.Instance.NextLoginTime);
                  }
      
                  // Support other code getting the time to next login with.
                  if (name == "GetNextLoginTime")
                  {
                      return AutoLoginSettings.Instance.NextLoginTime;
                  }
      
                  return null;
              }
      
      New AutoLogin plugin code:
      Code:
              /// <summary>
              /// Implements logic to handle a message passed through the system.
              /// </summary>
              /// <param name="message">The message to be processed.</param>
              /// <returns>A tuple of a MessageResult and object.</returns>
              public MessageResult Message(Message message)
              {
                  // Support other code setting the time to next login with.
                  if (message.Id == "SetNextLoginTime")
                  {
                      AutoLoginSettings.Instance.NextLoginTime = message.GetInput<DateTime>();
                      Log.InfoFormat("[AutoLogin] Execute({0}) = {1}.", message.Id, AutoLoginSettings.Instance.NextLoginTime);
                      return MessageResult.Processed;
                  }
      
                  // Support other code getting the time to next login with.
                  if (message.Id == "GetNextLoginTime")
                  {
                      message.AddOutput(this, AutoLoginSettings.Instance.NextLoginTime);
                      return MessageResult.Processed;
                  }
      
                  return MessageResult.Unprocessed;
              }
      
      Logic for setting data or calling logic does not change much. Logic for getting data does.

      Message.AddOutput is used to store a return value for processing by the caller. One thing that will most likely change is supporting associating a name with the object, for a bit more convenience in overcoming the limitations of type-less objects that have to be cast correctly. In any case though, AddOutput can be called any number of times to register multiple return values, which was not possible before.

      The caller who wants to process data from messages is now also changed. I should also note, most if not all current code does not do error checking, and assumes the types returned are correct, so there's less code in that regards anyways.

      Current StuckDetection plugin code:
      Code:
      var tii = (TimeSpan?) BotManager.CurrentBot.Execute("oldgrindbot_get_time_in_instance");
      
      New StuckDetection plugin code:
      Code:
      var msg1 = new Message("oldgrindbot_get_time_in_instance", this);
      var res1 = BotManager.CurrentBot.Message(msg1);
      var tii = msg1.GetOutput<TimeSpan?>();
      
      In the previous example, the design of the code is much better than before, and doesn't add too much extra code. GetOutput allows to specify parameters by index, default being 0, and soon even by names (if set).

      That wraps up this first set of changes for 3.0. This change is breaking in that it'll requite some function updates, but nothing too brutal. Most existing code simply implements an empty ILogic.Execute handler since it's not used for much yet, so a lot of copy and paste to replace the old function will be possible. This also applies to tasks, so the same is true there as well. Hopefully the reasoning behind this change makes sense, and will contribute to a more coherent development experience with the Exilebuddy API.
       
      Last edited: Mar 28, 2017
      LajtStyle likes this.
    3. pushedx

      pushedx Moderator Moderator Buddy Core Dev

      Joined:
      Sep 24, 2013
      Messages:
      4,252
      Likes Received:
      290
      Trophy Points:
      83
      "Logic" changes (ILogic.Logic, ITask.Logic, TaskManager)

      Along the theme of the previous post, "logic" based systems have gone a refresh as well. The main motivation behind these changes is to present a more intuitive system that helps devs avoid ambiguous mechanics while giving them the flexibility and power needed to do cool things.

      ILogic is now ILogicHandler.
      Code:
          /// <summary>
          /// An interface for an object that can handle dynamic logic implementations.
          /// </summary>
          public interface ILogicHandler
          {
              /// <summary>
              /// Implements the ability to handle a logic passed through the system.
              /// </summary>
              /// <param name="logic">The logic to be processed.</param>
              /// <returns>A LogicResult that describes the result..</returns>
              Task<LogicResult> Logic(Logic logic);
          }
      
      The LogicResult enum has two states:
      Code:
          /// <summary>
          /// Return value type for ILogic.Logic.
          /// </summary>
          public enum LogicResult
          {
              /// <summary>The logic was provided.</summary>
              Provided,
      
              /// <summary>The logic was not provided.</summary>
              Unprovided,
          }
      
      The idea behind the new Logic function is the same as the new Message function - more intuitive designs to help users avoid ambiguous or hidden behaviors. Logic that provides executed code should return
      LogicResult.Provided now rather than true, and LogicResult.Unprovided rather than false.

      Old AutoLogin code
      Code:
      public async Task<bool> Logic(string type, params dynamic[] param)
      {
          if (type == "login_screen_hook")
          {
              return await login_screen(type, param);
          }
      
          if (type == "character_selection_hook")
          {
              return await character_selection(type, param);
          }
      
          return false;
      }
      
      New AutoLogin code
      Code:
      public async Task<LogicResult> Logic(Logic logic)
      {
          if (type == "hook_login_screen")
          {
              if (await login_screen(logic))
              {
                  return LogicResult.Provided;
              }
              return LogicResult.Unprovided;
          }
      
          if (type == "hook_character_selection")
          {
              if (await character_selection(logic))
              {
                  return LogicResult.Provided;
              }
              return LogicResult.Unprovided;
          }
        
          return LogicResult.Unprovided;
      }
      
      Where the coroutines 'login_screen' and 'character_selection' are separate tasks that return true/false based on their logic which is processed by the caller.

      The intended use of Logic is to allow code to insert a placeholder call for external logic to be executed. Typically, this pattern is used for "hooks", but "non-hook" logic is also acceptable as long as it's not "event" or "message" based logic (in which case those should use the Message function instead).

      For example, in addition to the character selection and login screen hooks shown above, the "combat" message that is handled in any IRoutine is triggered from a combat task. The whole task is just calling Logic on the current routine, and letting the routine have full control over what happens. This pattern can me applied to a lot of other things as well for similar dynamic systems.

      These changes result in the proper handling of messages that have been incorrectly using Logic in the current system.

      "plugin_coroutine_event" - In current bot bases, this is called before the TaskManager polls its tasks.
      Code:
          foreach (var plugin in PluginManager.EnabledPlugins)
          {
              await plugin.Logic("plugin_coroutine_event");
          }
      
      The primary use of "plugin_coroutine_event" right now is in "CommonEvents", which performs a BroadcastEventLogic for various events, such as "core_player_died_event", "core_player_leveled_event", "core_area_changed_event". The original intention with this design was that when such an event happened, it'd be possible to run async code right away, in a hook-like fashion, except all handlers get called.

      The problem with the design is that it's never practical to execute a sequence of unrelated coroutines in such a manner, because it can result in unintended behaviors as soon as one of the handlers performs complex logic, like changing areas. While there are no known problems so far, it's a design flaw that needs to be addressed to improve there coherency of these core systems. In addition, it's extra overhead for no other reason than to support CommonEvents, which can easily be implemented in Tick and use messages!

      "plugin_coroutine_event" has been removed completely.

      As a result: "core_area_changed_event", "core_player_died_event", "core_player_leveled_event" are now no longer "Logic" messages, but rather Messages for IMessageHandlers, so all code that used those events in Logic will need to migrate the code to the new "Message" function and return the appropriate "MessageResult" value.

      This change will hopefully make things a bit more intuitive. Currently, it makes no sense you have to handle "core_player_died_event" in "Logic", and "Execute" doesn't get notified. Having to choose between coroutine and non-coroutine variants for "events" shouldn't be a thing, so now such "events" are treated as what they really are, "messages".

      * "core_exploration_complete_event" is also affected, but should have been named "ogb_exploration_complete_event", so that change is now in place.

      The following Logic messages have been renamed for consistency:
      "combat" -> "hook_combat"
      "login_screen_hook" -> "hook_login_screen"
      "character_selection_hook" -> "hook_character_selection"
      "post_combat_hook" -> "hook_post_combat"

      Additional hooks will be added to improve customization as part of other 3.0 updates.

      ITask no longer implements ILogic. Originally, the intended design was for them to share the same interface to cover overlap, but that system has caused a lot of confusion over time, and contributes to a non-intuitive design.

      "task_execute" - In current bot bases that make use of tasks, this is called on the TaskManager instance to transition logic into that provided by tasks.

      Current bot base code
      Code:
      // What the bot does now is up to the registered tasks.
      await _taskManager.Logic("task_execute");
      
      The idea behind this setup was to pass execution to the list of tasks managed by the task manager. This design has proved very valuable and served as the core of how logic was implemented in bots the past few years.

      The new code for that is as follows (name might change soon too):
      Code:
      // What the bot does now is up to the registered tasks.
      var logic = new Logic("task_execute", this);
      await _taskManager.RunTasks(TaskGroup.Enabled, RunBehavior.UntilHandled, logic);
      
      TaskManager is getting a few improvements to reflect how tasks are primary used now.

      TaskManager/Base.Execute is now "MessageTasks". This is to reflect changes made to ILogic.Execute, which is now IMessageHandlers.Message as talked about in the previous post.

      TaskManager/Base.Logic is now "RunTasks". This is to avoid confusion with calls to ILogic.Logic and better reflects what the coroutine does. The name "Execute" is not being used to avoid confusion with the previous ILogic function, and the past coroutine based Execute that preceded Logic.

      RunTasks now returns a RunTasksResult, which has values of "NoTasksRan" and "TasksRan" to report to calling logic whether or not tasks ran to handle the logic.

      In addition, RunTasks now takes a TaskGroup parameter at the start to tell the logic if all tasks should be run (All), enabled tasks should be run (Enabled), or if only disabled tasks should be run (Disabled). This change was done to provide more transparency as to what the coroutine is actually doing, as before only enabled tasks were ran.

      Finally, hidden mechanics of the old TaskManager.Run function have been removed. Previously, if the logic type ended with "_event", the function would discard return values and simply process all tasks. Otherwise, the task looping would stop as soon as a task returned true.

      Utility.BroadcastEventLogic was used to implement the now removed idea of using "events" via Logic, so that function has now been removed. Logic using BroadcastEventLogic needs to switch to the new Message systems instead, as that is the correct way to handle "events".

      The following events were changed as a result, and have been updated to the Message system:
      oldgrindbot_local_area_changed_event
      item_looted_event [EXtensions]
      player_resurrected_event [EXtensions]
      item_stashed_event [EXtensions]
      items_sold_event [EXtensions]
      explorer_local_transition_entered_event [EXtensions]
      map_trial_entered_event [EXtensions]

      That about covers the main logic based changes in place so far. It's important these core systems get the necessary updates, so new logic for 3.0 in our bots, plugins, and routines can properly take advantage of them. These breaking changes will result in quite a bit of code updates, but the updates themselves are really simple - moving code to different places and fixing return values from true/false to the appropriate enum.
       
      LajtStyle likes this.
    4. pushedx

      pushedx Moderator Moderator Buddy Core Dev

      Joined:
      Sep 24, 2013
      Messages:
      4,252
      Likes Received:
      290
      Trophy Points:
      83
      Project Reorganization

      Over the years, content has been added on and hacked into EB in order to have something that worked that was "good enough" at the time. This has resulted in the current bloated mess users see when they look inside the "3rdParty" folder.

      In order to address this for 3.0, a project reorganization is being done.

      The changes are pretty straight forward. There will now be two main assemblies moving forward in the 3rdParty folder by default. The old stuff that is no longer supported, but still being provided to reference or for people to migrate to new 3.0+ stuff will be in the "Legacy" folder. The new stuff from ExVault, and the future core content and specialty plugins we provide will be in the "Default" folder.

      There's quite a few motivations behind this change.

      When the current 3rdParty system was added a year ago or so, the project structure was not revamped at the time to better reflect what was possible (and really has been possible all along, but not many people are aware of it). Each "assembly" that is compiled from source and loaded into EB is not limited to any specific type or number of interface implementations (IBot, IRoutine, IPlugin, for example). This means that it's possible to have 1 assembly that has all your bots, plugins, and routines.

      Why does this matter? This matters because there's a fixed amount of overhead for compiling assemblies and loading them into EB in addition to the variable overhead of actually compiling the code. Let's say for example the fixed overhead is 10s per assembly. Right now EB comes with 24 assemblies that must be compiled and loaded each time the application is started. Users of the old system remember how slow EB was in starting, and the switch to multi-threaded compiling via Roslyn dramatically decreased that time, but did not address the core issue.

      Continuing on, 24 assemblies * 10s of fixed overhead is 240s + the variable compile time for each to compile, which let's say ranges between 1-3s each.

      Now, imagine there's only 2 assemblies to compile and load. That's 20s of fixed time, and now the compile time can be slightly longer because there's more code, but still less than the fixed overhead of before. Even if each assembly takes 10s to compile now (which it doesn't because that's not how compiling works) it should be obvious that's a huge difference from before.

      The effects of this change also help us greatly, as Visual Studio and the build server both experience the same issues with too many assemblies present. I've already noticed a huge speed up of VS, which will help contribute to more productivity in the long run since that's less wasted time waiting on slowdowns.

      In additional to the technical improvements this change will bring, quite a few aesthetic changes will come. 2 main folders from us in the 3rdparty folder is less confusing then the current setup. In line with the new settings window changes pending, we hope things just make more sense when looking at the layout of things.

      That sums up this next set of changes upcoming for 3.0. As each of these sets of changes is posted about, they are being tested in the Alpha build of EB and we hope to let interested devs start looking at the changes applied to 2.6 so they can get a head start on updating their stuff for the non-game specific changes we'ere doing for 3.0.
       
      Last edited: Mar 30, 2017
    5. pushedx

      pushedx Moderator Moderator Buddy Core Dev

      Joined:
      Sep 24, 2013
      Messages:
      4,252
      Likes Received:
      290
      Trophy Points:
      83
      Presenting IContent!

      As Exilebuddy has evolved over the years, there has been an emergence of "library" based plugins that offer various convenience wrappers and code for other content to make use of. The two main examples are CommunityLib and EXtensions. Another example, while much more simple, is CommonEvents.

      The current IPlugin structure does not really fit those types of content because an IPlugin can be enabled/disabled and is IRunnable (Start/Tick/Stop). Library-like content does not need to be enabled or disabled, and there's no inherent need to implement IRunnable code.

      What is still needed is everything else IPlugin offers, so a new interface has been added, IContent. This interface will solve a previously hacked around solution, the IMandatory interface, which would prevent IPlugin instances from being disabled. It was solely added to address the design problem CommonEvents posed: code needed to exist and always run because everything else was based on it.

      The CommonEvents plugin will need minor changes now in that all bot bases will need to explicitly make use of its code, but that's an acceptable trade-off as that's how every other library aspect of Exilebuddy has been setup for a while now. For example, if a bot base doesn't want to support Plugins, then it simply doesn't make use of the PluginManager. Likewise with Routines.

      Over the years, we've wanted less "hidden" systems in place running to ensure bot bases do exactly what they are coded to do, and devs don't have to be forced into using any specific designs we use, because there's always new and innovative ways to do things that we've might not have thought of yet.

      The addition of this interface, and support for it will help simplify some otherwise confusing aspects of the current design. Users will not longer need to worry about disabling EXtensions or CommunityLib, because there's no mechanics in place to disable them - either the content is loaded into the bot, or it's not!

      Along with the theme of the last post of being able to have more than one interface implementing object per assembly, future IContent implementations will be able to add all sorts of additional content to Exilebuddy without having to worry about the confusion it might bring if it implemented IEnableable.

      This change will minimally impact existing code. Only IBot implementations will need to make use of a new message added to trigger Tick-like logic in non-IRunnable content, but that's about it. This is as simple as adding:
      Code:
      Utility.BroadcastMessage(this, "core_tick"); // Tick all non-IRunnable content
      
      in the Tick function usually after ExilePather.Reload is called.
       
    6. pushedx

      pushedx Moderator Moderator Buddy Core Dev

      Joined:
      Sep 24, 2013
      Messages:
      4,252
      Likes Received:
      290
      Trophy Points:
      83
      TaskManager / ITask / Logic Updates Part 2

      A second pass of changes has been applied to the earlier post. To summarize the new changes:

      ILogicHandler -> ILogicProvider (there was a name mix-up that lead to handler being used rather than provider originally)

      ITask now implements ILogicProvider again (former ILogic) but still has a dedicated Run function.

      ITask.Run takes no Logic object, and now represents the "main" task logic that should run. The coroutine still has to return true if it executed and false if not.

      TaskManager has had various cleanups and renames to simplify code and make it more intuitive. Message -> SendMessage, RunTasks -> Run, ProvideLogic added to invoke Logic on tasks now.

      Only 4 plugins are affected by these design changes, which are plugins that use the "hook_post_combat" to run secondary tasks on a dedicated task manager (which that design will most likely change soon too). Those plugins now have a simple addition to their Logic coroutines to invoke TaskManager.Run on their private TaskManager instances for the "hook_post_combat" logic, as opposed to moving the handling of that logic to ILogicProvider.Logic and having an empty ITask.Run coroutine.

      That might sound confusing, but it should make more sense when you see it. Basically now, bot bases will run tasks by calling:
      Code:
      // What the bot does now is up to the registered tasks.
      await _taskManager.Run(TaskGroup.Enabled, RunBehavior.UntilHandled);
      
      Since PostCombatHookTask is implemented as a task that provides a logic hook:
      Code:
      public class PostCombatHookTask : ITask
          {
              public async Task<bool> Run()
              {
                  foreach (var plugin in PluginManager.EnabledPlugins)
                  {
                      if (await plugin.Logic(new Logic("hook_post_combat", this)) == LogicResult.Provided)
                      {
                          GlobalLog.Info($"[PostCombatHookTask] \"{plugin.Name}\" returned true.");
                          return true;
                      }
                  }
                  return false;
              }
      ...
      
      The plugins: AutoPassives, Breaches, GemLeveler, and Monoliths will receive the "hook_post_combat" logic via their own IPlugin.Logic function, like so:
      Code:
      public async Task<LogicResult> Logic(Logic logic)
              {
                  if (logic.Id == "hook_post_combat")
                  {
                      return await _taskManager.Run(TaskGroup.Enabled, RunBehavior.UntilHandled) == RunTasksResult.TasksRan
                          ? LogicResult.Provided
                          : LogicResult.Unprovided;
                  }
                  return await _taskManager.ProvideLogic(TaskGroup.Enabled, logic);
              }
      
      Since they maintain their own TaskManager to add their tasks to run at specific times (after combat as per the hook), the actual task logic can stay in the ITask.Run coroutine rather than being process as the original Logic object, because the tasks were designed to only perform logic for that one specific hook anyways.

      Since the plugins tasks are not a part of the main bot base TaskManager, their Run coroutine will not be called any other way since the plugin controls it solely.

      As a whole, the annoyances of almost all tasks (except of the secondary nature type just mentioned) having to process "task_execute" is now gone. The ITask.Run function replaces that mechanic, and will hopefully lead to much cleaner task code moving forward.
       
    7. pushedx

      pushedx Moderator Moderator Buddy Core Dev

      Joined:
      Sep 24, 2013
      Messages:
      4,252
      Likes Received:
      290
      Trophy Points:
      83
      Interface Changes Part 2

      Summary of changes:
      • IPlugin no longer forces an implementation of IRunnable
      • IRunnable split into two new interfaces
      • ITickEvents.Tick to receive Tick events
      • IStartStopEvents.Start/Stop to receive Start/Stop events
      • Existing plugins now only implement the relevant ITickEvents /IStartStopEvents interfaces.
      • PluginManager and the settings window now allow plugins that don't implement IStartStopEvents to be enabled/disabled at runtime.
      Examples of current changes:

      Old:
      Code:
      internal class StuckDetection : IPlugin
      internal class AreaVisualizer : IPlugin
      internal class AutoFlask : IPlugin
      internal class AutoLogin : IPlugin
      internal class AutoPassives : IPlugin
      internal class Breaches : IPlugin
      internal class Monoliths : IPlugin
      internal class ItemFilterEditor : IPlugin
      internal class GemLeveler : IPlugin
      internal class Chicken : IPlugin
      public class Leaguestones : IPlugin
      
      New:
      Code:
      internal class StuckDetection : IPlugin, ITickEvents
      internal class AreaVisualizer : IPlugin, ITickEvents
      internal class AutoFlask : IPlugin, ITickEvents
      internal class AutoLogin : IPlugin, ITickEvents
      internal class AutoPassives : IPlugin, IStartStopEvents, ITickEvents
      internal class Breaches : IPlugin, IStartStopEvents, ITickEvents
      internal class Monoliths : IPlugin, IStartStopEvents, ITickEvents
      internal class ItemFilterEditor : IPlugin, IStartStopEvents
      internal class GemLeveler : IPlugin, IStartStopEvents, ITickEvents
      internal class Chicken : IPlugin, ITickEvents
      public class Leaguestones : IPlugin, IStartStopEvents
      
      Other plugins, such as DevTab, ObjectExplorer, and Stats, to name a few, do not need either IStartStopEvents, nor ITickEvents, so their interface declaration is still the same. The Start/Tick/Stop implemented functions have just been removed.

      The motivation behind this update is to finally allow plugins designed to be enabled/disabled during runtime the capability to do so. Previously, all plugins were forced into the "enable/disable when bot isn't running model", but there's no reason to keep that.

      Plugins that implement on IStartStopEvents cannot be allowed to be enabled/disabled during runtime though, by design, because they are allowed to make certain changes that affect the bot as it's running, and would result in breaking behavior.

      However, most of the current legacy plugins do not actually need to implement IStartStopEvents, and only do so due to an old design decision to implement additional logic via a secondary TaskManager. The logic for those plugins is being reworked for 3.0 so this is no longer the case.

      This means, for most default plugins, you'll be able to enable/disable at runtime without any side-effects, which is a big QoL improvement over the current system.

      For developers, they must go through all their IPlugin implementations and add the appropriate interfaces to regain Tick and Start/Stop events. They can remove the old placeholder functions that did nothing about output the function name as well.
       
    8. pushedx

      pushedx Moderator Moderator Buddy Core Dev

      Joined:
      Sep 24, 2013
      Messages:
      4,252
      Likes Received:
      290
      Trophy Points:
      83
      The next set of changes for Alpha involve a simplification in the Item object wrappers. Essentially, the sub-item wrappers that implement additional item data exposing, have been folded into the main Item class. This eliminates some redundancy where the main Item class already wrapped those sub-item specific members, and removes the need for various object casts to access data that is exposed via components.

      The changes are as follows:
      • Objects.Items revamps, optimizations, and caching.
        • SocketableItem removed. [Use Item.ObjectTypeIsSocketableItem rather than 'Item is SocketableItem']
          • SocketedSkillGemsByLinks now returns a List rather than an IEnumerable.
          • LinkedSocketColors now returns a List rather than an IEnumerable.
        • Currency removed. [Use Item.ObjectTypeIsCurrency rather than 'Item is Currency']
        • Armor removed. [Use Item.ObjectTypeIsArmor rather than 'Item is Armor']
        • Map removed. [Use Item.ObjectTypeIsMap rather than 'Item is Map']
          • MonsterPackSize -> MapMonsterPackSize
          • ItemQuantity -> MapItemQuantity
          • ItemRarity -> MapItemRarity
          • RawItemRarity -> MapRawItemRarity
          • RawItemQuantity -> MapRawItemQuantity
          • Tier -> MapTier
          • Level -> MapLevel
          • Area -> MapArea
        • Shield removed. [Use Item.ObjectTypeIsShield rather than 'Item is Shield']
        • SealedProphecy removed. [Use Item.ObjectTypeIsSealedProphecy rather than 'Item is SealedProphecy']
          • Difficulty -> ProphecyDifficulty
        • Weapon removed. [Use Item.ObjectTypeIsWeapon rather than 'Item is Weapon']
        • Flask removed. [Use Item.ObjectTypeIsFLask rather than 'Item is Flask']
        • Leaguestone removed. [Use Item.ObjectTypeIsLeaguestone rather than 'Item is Leaguestone']
        • SkillGem removed. [Use Item.ObjectTypeIsSkillGem rather than 'Item is SkillGem']
          • GemTypes now returns a List rather than an IEnumerable.
          • GetAttributeRequirements now returns a bool if the function was performed or not.
          • Level -> SkillGemLevel

      Some simply named properties have had to be renamed to avoid conflicts or easy confusion with others. There still exists some possible confusion, but the VS docs that exist for members clarifies what a property is used for.

      Code changes will mostly be these things:
      - Code that used "items.OfType<class>()" now switches to "items.Where(i=>i.ObjectTypeXX)"
      - Code that checked "item is class" now just checks "item.ObjectTypeXX"
      - Code that cast "item as class" no longer needs the cast
      - Previously mentioned renamed members need to be updated

      The goal of this set of changes is to simplify some otherwise annoying and awkward aspects of the old Item system where only a few types of sub-item wrappers existed, but to access their members, devs had to cast the Item object. Now, that is no longer needed, so using the Item API is more friendly in scripted setups and is more consistent.

      All properties will do proper checking to ensure exceptions are not thrown from accessing members that do not apply for an item. Default values are sensible for the most part as well, and null is only returned for a few specific members that return Dat based objects.
       
    9. pushedx

      pushedx Moderator Moderator Buddy Core Dev

      Joined:
      Sep 24, 2013
      Messages:
      4,252
      Likes Received:
      290
      Trophy Points:
      83
      Item Updates Part 2

      The next set of item updates has just been completed. This set of changes revolves around the metadata processing properties that have been slowly added to Item over time. The aim of these was to ease some common metadata processing, but the result is that there's now 40+ of them, and they add a lot of extra overhead due to their setup.

      To start, this is what the old code looked like: https://gist.github.com/pushedx/166d03e9d7aee71ecb6c9a3dcecbab93

      The problem with this setup was:
      - The naming doesn't reflect the intent. These are for metadata matching, but the name implies logical item type.
      - String parsing overhead can add up over time
      - Only a limited set of metadata processing exists
      - This was all manual work and maintenance. While metadata doesn't change really, it's not a good system to rely on for the future.

      The new system in place uses code generation to create a lookup of all metadata tokens per metadata entry in BaseItemTypes, and then creates a Dictionary lookup of each metadata token group. This means checking for specific metadata tokens no longer uses string operations, as the metadata tokens are now expressed as Enums which help with type safety and catching errors when things change (much like how StatTypeGGG works).

      Now to cover how this affects code. All "IsXXType" Item properties are being removed. The new system in place will be used to replace that functionality, so nothing is lost. Here is a reference of how updates would be done for those properties: https://gist.github.com/pushedx/2628cf2710c87eba97742577fc3b29ea

      For example, any place where item.IsHelmetType existed, now you'd use
      item.HasMetadataFlags(MetadataFlags.Helmets);

      The "tokens" are simply the parts of the metadata string, which the reference of all items' metadata can be obtained from running the DumpBaseItemType.cs script from the DevTab. For example a few helmet entries are as follows:
      Code:
      1313,Metadata/Items/Armours/Helmets/HelmetStr1,"Iron Hat",Helmet
      1314,Metadata/Items/Armours/Helmets/HelmetStr2,"Cone Helmet",Helmet
      1315,Metadata/Items/Armours/Helmets/HelmetStr3,"Barbute Helmet",Helmet
      
      IsHelmetType simply looks for the "Helmets" part of the metadata, as before it did a string match for "/Helmets/".

      The actual list of enums for MetadataFlags can also be generated by users from the "GenerateBaseItemTypesComposite.cs" script that will be included with the bot, in the Scripts folder along with the other tools used to generate various data the bot uses. It'll also be available through visual studio and the help file too.

      With this system in place, working with metadata matching for items in code will be a lot more intuitive. Users can still perform their own string matching on Metadata if they want, but the core API is moving on to a more efficient system.
       
    10. pushedx

      pushedx Moderator Moderator Buddy Core Dev

      Joined:
      Sep 24, 2013
      Messages:
      4,252
      Likes Received:
      290
      Trophy Points:
      83
      One change that was done that I missed mentioning was the order in which content is loaded.

      In 2.6, zips were loaded first, and then folders. I forgot why I had things setup this way, but I thought the idea was that people would rely on zips more so than folders. As it turned out, more people kept extracting zips (as much as we tried to raise awareness they didn't need to) and modified files in the folder.

      As a result, it was brought up many times that the load order of zips then folders didn't make sense, so this was changed.

      For 3.0, folders are loaded first, and then zips. The reasoning behind this is for the general case of users downloading a zip, extracting it, then changing files and not deleting the source zip. We felt this was more the common way people were using things, so the design was updated for it.

      Mixing zips and folders is not recommended though. There's no way for EB to guess your intention when you have both, so that's why it choose folders first, then zips (and previously zips then folders).
       
    Thread Status:
    Not open for further replies.

    Share This Page