Skip to content

Code documentation

Bartosz Korczyński edited this page Jul 20, 2022 · 7 revisions

This page contains various basic information about the code structure and how things are designed here.

-- WIP --

General

MVVM and ViewModel first approach

The editor uses MVVM (Model-view-view model) pattern, in particular it uses View Model first (instead of View first) approach. Explaining MVVM is out of this wiki scope, if you want to read more about it, just google for MVVM, e.g. https://docs.avaloniaui.net/guides/basics/mvvm .

Regarding the 'View Model first' approach - it means everywhere in the code we are referring to the view model. And the view is created automatically underneath. E.g. if you want to display a new dialog window, you should create a View Model for the window and the view will be created automatically. In the other approach (View first) you would create a view instance first and the view model would be created automatically. This however couples your UI framework, meanwhile, view models in WoW Database Editor are view agnostic. In the past there were two versions - one using WPF, the other one using Avalonia, both reusing the same Models and View Models, while only views were different.

Modules

Wow Database Editor is built in a modular way, that means projects are de-coupled from each other and they don't know about other projects whenever possible. It is even possible to load an external .dll as a plugin (even though it is not used now). Things are resolved dynamically, usually using shared interfaces, defined in the project WDE.Common.

The core project (sometimes referred as a "shell") doesn't even know what particular things can be edited, all the editors like 'Creature script', 'WoW Sniff', 'Creature texts' are resolved dynamically, all the tool windows like 'Tables', 'Sessions', 'History' are resolved dynamically. image

Registering Views by convention

By default, when you are about to display some View Model, View is resolved automatically by name convention - ViewModel in class full name is replaced with View. E.g. if you have class WDE.SmartScriptEditor.ViewModels.EditorViewModel, the editor will try to instantiate class WDE.SmartScriptEditor.Views.EditorView as a view.

Alternatively, you can manually assign a view to your viewmodel, in the method public override void RegisterViews(IViewLocator viewLocator) in your module class (class that extends ModuleBase)

Common interfaces

This chapter briefly describes the common interfaces used to implement editing features.

WDE.Common.Windows.ITool

Implement this interface, to add a new tool window, like 'Tables', 'Sessions', 'History'. Once you add a class that implements this interface, the editor will automatically find it and add it to 'Views' menu, so that the user is able to open it.

image

Note: please don't confuse the interface with Dock.Model.Controls.ITool which is also defined in the solution.

WDE.Common.Managers.IDocument

The most important and basic interface representing a basic document that can be opened in the editor. It contains basic data like Title, Icon of the document, Cut, Paste, Copy commands implementation for this document. It doesn't have to be wow-related, it doesn't have to be save-able. I.e. "Quick start" is a document, "Settings" is a document. But also each editor (smart script editor, table editor) is a document.

Note: please don't confuse the interface with Dock.Model.Controls.IDocument which is also defined in the solution.

WDE.Common.ISolutionItem

This is the most basic interface, the most basic unit that represents an editable thing, that represents a thing that can be opened, edited, saved, a thing that can be stored in a solution or in a session.

image

The Solution Explorer tool contains ISolutionItems. E.g. a folder is implemented by WDE.Solutions.SolutionFolderItem class, smart scripts are implemented by WDE.SmartScriptEditor.SmartScriptSolutionItem class, table editors are implemented by WDE.DatabaseEditors.Solution.DatabaseTableSolutionItem class.

Those ISolutionItem are serialized automatically by Newtonsoft.Json library, so if there is something you don't want to be serialized, you might need to use additional attributes on fields/properties, e.g. look at SmartScriptSolutionItem class ([JsonIgnore] attributes) or DatabaseTableSolutionItem class ([JsonProperty] attributes).

Related interfaces

There is a series of ISolutionItem related interfaces that provide additional data regarding the solution item, e.g. name or icon.

note: T there will be your particular ISolutionItem implementation

  • ISolutionNameProvider<T> - implement this interface and method string GetName(T item) to provide a name for a solution item (must have).
  • ISolutionItemSqlProvider<T> - implement this interface and method Task<IQuery> GenerateSql(T item) to provide a method that generates a query for this item (must have).
  • ISolutionItemIconProvider<T> - implement this interface and method ImageUri GetIcon(T item) to provide an icon for a solution item. E.g. icons in the solution explorer tool are resolved using this interface (pretty much must have).
  • ISolutionItemSerializer<T> and ISolutionItemDeserializer<T> - implement this interfaces as an alternative method of serializing and deserializing solution items (must have). Note: Type field in ISmartScriptProjectItem must be unique per solution item and also is now an arbitrary number, which is not the best design decision (considering we support modularity).
  • ISolutionItemEditorProvider<T> - implement this interface and method IDocument GetEditor(T item) to construct a view model (which implements IDocument) for this solution item
  • ISolutionItemRemoteCommandProvider<T> - implement this interface and method IRemoteCommand[] GenerateCommand(T item) to generate a series of server commands that will be remotely executed to reload this solution item. E.g. editing creature_template of entry X, can return .reload creature_template X command (optional)
  • ISolutionItemRelatedProvider<T> - implement this interface and method Task<RelatedSolutionItem?> GetRelated(T item) to provide information what 'related' things can be opened (icons in the top right corner) (optional)
image
  • ISolutionItemDocument - this is an extension of IDocument interface, it additionally contains ISolutionItem SolutionItem { get; } property and Task<IQuery> GenerateQuery(). All the 'document editors' view models should implement this interface. _todo: I guess ISolutionItemEditorProvider<T> should return ISolutionItemDocument instead of IDocument then.

ISolutionItemProvider

Implement this interface, to provide a "factory" class for your T : ISolutionItem. It contains a "name", "description" and "icon" of the thing that can be created using this interface. It has a method Task<ISolutionItem?> CreateSolutionItem() used to create a new solution item. This method returns a Task, which means you can open additional async windows, e.g. open a window to pick a creature entry, which later will be used to construct your ISolutionItem instance.

In this interface you can also decide whether your particular ISolutionItem makes sense with currently selected ICoreVersion (= emulator version/type). E.g. Smart Scripts are not supported by (c)mangos, they are supported by Trinity based emulators tho. Therefore a provider for SmartScriptSolutionItem can decide whether the current core version is Trinity or Mangos and allow creating this type of solution item or not.

image

(Quick load and File -> New/Open are added dynamically based on ISolutionItemProvider implementations).

Brief recap:

image

To sum this up, the fact that Creature Script is visible in the Quick Load panel means, there is some ISolutionItemProvider implementation for this.

When I click on the creature script button, it will execute ISolutionItemProvider::CreateSolutionItem() method, which will create a new ISolutionItem. In case of SAI Creature Script, it will create a new SmartScriptSolutionItem instance with chosen "entryorguid" and SmartScriptType.Creature enum as type.

Once the SmartScriptSolutionItem is created, in order to display an editor for this item, a document-editor needs to be created. This means the editor will look up for a class that implements ISolutionItemEditorProvider<SmartScriptSolutionItem> and it will invoke GetEditor() method, in case of SAI, check the WDE.TrinitySmartScriptEditor.Providers.SmartScriptEditorProvider class.

Generic table editors

image

In some cases it makes sense to create a specialized editor, e.g. smart_scripts internal structure (in the database) is a little bit compound (e.g. a way to link multiple actions), therefor SmartScripts has a special editor displaying events and actions.

However, in some cases there is no way to create anything but a generic view for table, columns and row. In this case WDE.DatabaseEditors project is used. Both the tables view on the left and the game_event editor is provided by this project.

It is fully generic, i.e. it loads the tables definition from json files. The json files are stored in WDE.DatabaseEditors/DbDefinitions folder.

You might ask: if this is generic, why do we even need a json definition, why not generate it automatically using database columns? The thing is, this is a generic view, but thanks to the definition, the editor can interpret the data in a specific way. I.e. TrinityCore's holiday_dates table has a column date_value which is a number - seconds since 1st of January 2000 (a unix epoch time with an offset). Thanks to the provided definition, the editor will display the date in a human readable form: image

Table editor modes

The generic table editor can work in three different modes, depending on a field record_mode in the json definition. Depending on the mode, the view is different and the generated query may vary.

Template

image

Template mode can be used for tables that have a primary key consisting of one column, i.e. creature_template. In this mode, the user has to pick the key that one wants to edit. This view presents table "column" in a vertical form - as rows. This is mainly due to the fact that _template tables usually have a lot of columns and scrolling horizontally is extremely inconvenient while vertical view makes much more sense.

image

you can also use search option to quickly find a field by name.

The generated query will always be update, unless you insert a completely new key. This is due to the fact, that we shall never delete + insert from _template tables (for retail-like content). If however you are creating custom stuff, you can insert a new key and it will correctly generate insert query.

SingleRow

image

SingleRow mode is the most universal, it can be used for tables with any primary key, as long as the columns in the key are numbers only (so no strings in table key). The view is very similar to a generic SQL viewers, but it makes use of the provided definition and presents the fields in a special way. I.e. in the screenshot above, Start/End Time columns are unix timestamp in the database, here displayed in a human-friendly form, Holiday is an entry from a DBC, here the corresponding name is shown.

In this mode, the user don't select any key to display, this view always displays all the rows, with pagination (in the bottom right corner) and with an ability to write a custom SQL query to filter.

The user can delete rows (DELETE query will be generated), can insert new rows (INSERT query will be generated), can edit non-key columns (UPDATE query will be generated) and can edit key columns (DELETE + INSERT will be generated, because changing a key is treated as a deletion + insertion in the editor).

MultiRecord

image

MultiRecord mode is the most specific one. It can be used for tables whose primary key contains at least two columns, yet one of them is usually the one the user want to edit it by. I.e. Trinity's creature_text has a primary key of (CreatureId, GroupId, Id). Creature id is a creature entry, while group id and id are just some consecutive numbers per entry. In other words: it doesn't make sense to edit (at once) 'all the creature texts, that has group id 1', yet it makes a lot of sense to edit (at once) 'all the creature texts, that belongs to creature id XYZ'

In this mode, the user has to select the key one wants to edit. And despite the primary key having multiple columns, the user selects just one column, e.g. in the creature_text example, the user has to select the creature entry whose texts we want to edit.

In the view there is no pagination, there is no filtering, because by design we already see the rows which matches our selected key.

Also, when it comes to the query generation, it never generates UPDATE query, it always generates DELETE + INSERT query by the selected key.

Clone this wiki locally