added proxy extension methods

fixes #2
This commit is contained in:
alterNERDtive 2022-07-11 10:25:06 +02:00
parent 5b6db94bce
commit 6636a0ed4e
7 changed files with 228 additions and 66 deletions

View file

@ -165,9 +165,9 @@ namespace alterNERDtive.Example
/// execute anything when all commands are stopped this is the place.
/// </summary>
[StopCommand]
public static void Stop()
public static void StopCommand()
{
Plugin.Log.Notice("This is the example Stop handler method.");
Plugin.Log.Notice("This is the example StopCommand handler method.");
}
/// <summary>
@ -186,7 +186,7 @@ namespace alterNERDtive.Example
Plugin.Log.Notice(
$"This is the example handler for the plugin contexts “test” and “different test”. It has been invoked with '{vaProxy.Context}'.");
string test = Plugin.Get<string?>("~test") ?? throw new ArgumentNullException("~test");
string test = vaProxy.Get<string?>("~test") ?? throw new ArgumentNullException("~test");
Plugin.Log.Notice($"The value of 'TXT:~test' is '{test}'");
}

View file

@ -1 +1,20 @@
global using Xunit;
// <copyright file="Usings.cs" company="alterNERDtive">
// Copyright 2022 alterNERDtive.
//
// This file is part of YAVAPF.
//
// YAVAPF is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// YAVAPF is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with YAVAPF. If not, see &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
global using Xunit;

View file

@ -0,0 +1,134 @@
// <copyright file="VoiceAttackProxyExtension.cs" company="alterNERDtive">
// Copyright 2022 alterNERDtive.
//
// This file is part of YAVAPF.
//
// YAVAPF is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// YAVAPF is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with YAVAPF. If not, see &lt;https://www.gnu.org/licenses/&gt;.
// </copyright>
using System;
using System.IO;
using VoiceAttack;
namespace alterNERDtive.Yavapf
{
/// <summary>
/// Extends the <see cref="VoiceAttackInitProxyClass"/> class with generic
/// methods for getting and setting VoiceAttack variables.
/// </summary>
public static class VoiceAttackInitProxyExtension
{
/// <summary>
/// Gets the value of a variable from VoiceAttack.
///
/// Valid varible types are <see cref="bool"/>, <see cref="DateTime"/>,
/// <see cref="decimal"/>, <see cref="int"/> and <see cref="string"/>.
/// </summary>
/// <typeparam name="T">The type of the variable.</typeparam>
/// <param name="vaProxy">The <see cref="VoiceAttackInitProxyClass"/> object.</param>
/// <param name="name">The name of the variable.</param>
/// <returns>The value of the variable. Can be null.</returns>
/// <exception cref="InvalidDataException">Thrown when the variable is of an invalid type.</exception>
public static T? Get<T>(this VoiceAttackInitProxyClass vaProxy, string name)
{
dynamic? ret = typeof(T) switch
{
Type boolType when boolType == typeof(bool) => vaProxy.GetBoolean(name),
Type dateType when dateType == typeof(DateTime) => vaProxy.GetDate(name),
Type decType when decType == typeof(decimal) => vaProxy.GetDecimal(name),
Type intType when intType == typeof(int) => vaProxy.GetInt(name),
Type stringType when stringType == typeof(string) => vaProxy.GetText(name),
_ => throw new InvalidDataException($"Cannot get variable '{name}': invalid type '{typeof(T).Name}'."),
};
return ret;
}
/// <summary>
/// Sets a variable for use in VoiceAttack.
///
/// Valid varible types are <see cref="bool"/>, <see cref="DateTime"/>,
/// <see cref="decimal"/>, <see cref="int"/> and <see cref="string"/>.
/// </summary>
/// <typeparam name="T">The type of the variable.</typeparam>
/// <param name="vaProxy">The <see cref="VoiceAttackInitProxyClass"/> object.</param>
/// <param name="name">The name of the variable.</param>
/// <param name="value">The value of the variable. Can not be null.</param>
/// <exception cref="InvalidDataException">Thrown when the variable is of an invalid type.</exception>
public static void Set<T>(this VoiceAttackInitProxyClass vaProxy, string name, T? value)
{
if (value == null)
{
vaProxy.Unset<T>(name);
}
else
{
switch (value)
{
case bool b:
vaProxy.SetBoolean(name, b);
break;
case DateTime d:
vaProxy.SetDate(name, d);
break;
case decimal d:
vaProxy.SetDecimal(name, d);
break;
case int i:
vaProxy.SetInt(name, i);
break;
case string s:
vaProxy.SetText(name, s);
break;
default:
throw new InvalidDataException($"Cannot set variable '{name}': invalid type '{typeof(T).Name}'.");
}
}
}
/// <summary>
/// Unsets a variable for use in VoiceAttack (= sets it to null).
///
/// Valid varible types are <see cref="bool"/>, <see cref="DateTime"/>,
/// <see cref="decimal"/>, <see cref="int"/> and <see cref="string"/>.
/// </summary>
/// <typeparam name="T">The type of the variable.</typeparam>
/// <param name="vaProxy">The <see cref="VoiceAttackInitProxyClass"/> object.</param>
/// <param name="name">The name of the variable.</param>
/// <exception cref="InvalidDataException">Thrown when the variable is of an invalid type.</exception>
public static void Unset<T>(this VoiceAttackInitProxyClass vaProxy, string name)
{
switch (typeof(T))
{
case Type boolType when boolType == typeof(bool):
vaProxy.SetBoolean(name, null);
break;
case Type dateType when dateType == typeof(DateTime):
vaProxy.SetDate(name, null);
break;
case Type decType when decType == typeof(decimal):
vaProxy.SetDecimal(name, null);
break;
case Type intType when intType == typeof(int):
vaProxy.SetInt(name, null);
break;
case Type stringType when stringType == typeof(string):
vaProxy.SetText(name, null);
break;
default:
throw new InvalidDataException($"Cannot set variable '{name}': invalid type '{typeof(T).Name}'.");
}
}
}
}

View file

@ -143,16 +143,13 @@ namespace alterNERDtive.Yavapf
/// <exception cref="InvalidDataException">Thrown when the variable is of an invalid type.</exception>
protected T? Get<T>(string name)
{
dynamic? ret = typeof(T) switch
if (name.StartsWith("~"))
{
Type boolType when boolType == typeof(bool) => this.Proxy.GetBoolean(name),
Type dateType when dateType == typeof(DateTime) => this.Proxy.GetDate(name),
Type decType when decType == typeof(decimal) => this.Proxy.GetDecimal(name),
Type intType when intType == typeof(int) => this.Proxy.GetInt(name),
Type stringType when stringType == typeof(string) => this.Proxy.GetText(name),
_ => throw new InvalidDataException($"Cannot get variable '{name}': invalid type '{typeof(T).Name}'."),
};
return ret;
this.Log.Warn(
$"Accessing command scoped variable '{name}' outside of its context proxy object. This might lead to race conditions.");
}
return this.Proxy.Get<T>(name);
}
/// <summary>
@ -167,33 +164,13 @@ namespace alterNERDtive.Yavapf
/// <exception cref="InvalidDataException">Thrown when the variable is of an invalid type.</exception>
protected void Set<T>(string name, T? value)
{
if (value == null)
if (name.StartsWith("~"))
{
this.Unset<T>(name);
}
else
{
switch (value)
{
case bool b:
this.Proxy.SetBoolean(name, b);
break;
case DateTime d:
this.Proxy.SetDate(name, d);
break;
case decimal d:
this.Proxy.SetDecimal(name, d);
break;
case int i:
this.Proxy.SetInt(name, i);
break;
case string s:
this.Proxy.SetText(name, s);
break;
default:
throw new InvalidDataException($"Cannot set variable '{name}': invalid type '{typeof(T).Name}'.");
}
this.Log.Warn(
$"Accessing command scoped variable '{name}' outside of its context proxy object. This might lead to race conditions.");
}
this.Proxy.Set<T>(name, value);
}
/// <summary>
@ -207,26 +184,13 @@ namespace alterNERDtive.Yavapf
/// <exception cref="InvalidDataException">Thrown when the variable is of an invalid type.</exception>
protected void Unset<T>(string name)
{
switch (typeof(T))
if (name.StartsWith("~"))
{
case Type boolType when boolType == typeof(bool):
this.Proxy.SetBoolean(name, null);
break;
case Type dateType when dateType == typeof(DateTime):
this.Proxy.SetDate(name, null);
break;
case Type decType when decType == typeof(decimal):
this.Proxy.SetDecimal(name, null);
break;
case Type intType when intType == typeof(int):
this.Proxy.SetInt(name, null);
break;
case Type stringType when stringType == typeof(string):
this.Proxy.SetText(name, null);
break;
default:
throw new InvalidDataException($"Cannot set variable '{name}': invalid type '{typeof(T).Name}'.");
this.Log.Warn(
$"Accessing command scoped variable '{name}' outside of its context proxy object. This might lead to race conditions.");
}
this.Proxy.Unset<T>(name);
}
/// <summary>

View file

@ -36,7 +36,8 @@ Separate functionality, separate handler method(s).
[Context("test")]
[Context("test context")]
[Context("alternate context name")]
public static void TestContext(VoiceAttackInvokeProxyClass vaProxy) {
public static void TestContext(VoiceAttackInvokeProxyClass vaProxy)
{
[…]
}
```
@ -68,7 +69,8 @@ conditionals:
[Context(@"^.*bar.*")]
[Context(@"^.*baz")]
[Context("some name")]
public static void RegexContext(VoiceAttackInvokeProxyClass vaProxy) {
public static void RegexContext(VoiceAttackInvokeProxyClass vaProxy)
{
string context = vaProxy.Context;
if (context.StartsWith("foo")) {
[…]
@ -93,7 +95,8 @@ intended to be used in practice:
```csharp
[Context(@"^edsm\..*")]
public static void EdsmContext(VoiceAttackInvokeProxyClass vaProxy) {
public static void EdsmContext(VoiceAttackInvokeProxyClass vaProxy)
{
switch(vaProxy.Context)
{
case "edsm.findsystem":
@ -129,7 +132,8 @@ again, doesnt matter.
```csharp
[Context]
public static void CatchallContext(VoiceAttackInvokeProxyClass vaProxy) {
public static void CatchallContext(VoiceAttackInvokeProxyClass vaProxy)
{
switch (vaProxy.Context)
{
case "some context":
@ -158,14 +162,20 @@ invocations and their data.
This example accesses the `~test` text variable from plugin code:
```csharp
string? testParameter = Plugin.Get<string>("~test");
string? testParameter = vaProxy.Get<string>("~test");
```
In case a parameter is missing that is _required_ for your context `throw` an
`ArgumentNullException` with the variable name as the parameter name:
```csharp
string testParameter = Plugin.Get<string>("~test") ?? throw new ArgumentNullException("~test");
string testParameter = vaProxy.Get<string>("~test") ?? throw new ArgumentNullException("~test");
```
**Note**: You should always use the `Get<T>(string)` and `Set<T>(string)`
extension methods of the `VoiceAttackInvokeProxyClass` object when accessing
command scoped variables. They are only available in the proxy object passed to
the corresponding context handler; the cached proxy object of your plugin object
might have changed already, leading to race conditions.
[More about variables](variables.md).

View file

@ -22,7 +22,8 @@ must have an `InitAttribute`. `InitAttribute` does not have any properties.
```csharp
[Init]
public static void MyInitHandler(VoiceAttackInitProxyClass vaProxy) {
public static void MyInitHandler(VoiceAttackInitProxyClass vaProxy)
{
[…]
}
```
@ -38,7 +39,8 @@ have an `ExitAttribute`. `ExitAttribute` does not have any properties.
```csharp
[Exit]
public static void MyExitHandler(VoiceAttackProxyClass vaProxy) {
public static void MyExitHandler(VoiceAttackProxyClass vaProxy)
{
[…]
}
```
@ -55,7 +57,8 @@ respond to that, use these.
```csharp
[StopCommand]
public static void MyStopCommandHandler() {
public static void MyStopCommandHandler()
{
[…]
}
```

View file

@ -61,6 +61,38 @@ By default, YAVAPF automatically sets the following variables for your plugin:
| `<plugin name>.version` | string | The current version of your plugin.
| `<plugin name>.initialized` | bool | The plugin has been initialized correctly.
## Using Proxy Methods vs. Using Plugin Methods
YAVAPF extends the `VoiceAttackInitProxyClass` and `VoiceAttackInvokeProxyClass`
classes with new generic methods for getting and setting variables. On top of
that `VoiceAttackPlugin` objects provide the same methods for ease of use and
when no proxy object is readily available to the current code path.
There is one **caveat** here: plugin objects cache a new proxy object every time
a plugin context is invoked. If you need to access command scoped variables (`~`
or `~~`), you should use the `VoiceAttackInvokeProxyClass` object directly.
Otherwise you might run into race conditions.
Correct:
```csharp
[Context("test")]
private static void TestContext(VoiceAttackInvokeProxyClass vaProxy)
{
string test = vaProxy.Get<string>("~test");
}
```
Incorrect, might lead to race condition:
```csharp
[Context("test")]
private static void TestContext(VoiceAttackInvokeProxyClass vaProxy)
{
string test = Plugin.Get<string>("~test");
}
```
## Getting Variable Values
To get the value of a variable, invoke the `Get<T>(string name)` method of your
@ -69,7 +101,7 @@ its scope:
```csharp
string? foo = Plugin.Get<string>("foo");
bool bar = Plugin.Get<bool>("~bar") ?? false;
bool bar = vaProxy.Get<bool>("~bar") ?? false;
```
Remember that variable values will be returned as `null` (“Not Set” in