#pragma once #include "interop-exception.h" #include #include using namespace System; using namespace System::Collections::Generic; using namespace System::Runtime::InteropServices; using namespace System::Reflection; using namespace System::Runtime::Serialization; using namespace Newtonsoft::Json; using namespace Newtonsoft::Json::Linq; namespace ra = rawaccel; ra::settings default_settings; [JsonConverter(Converters::StringEnumConverter::typeid)] public enum class AccelMode { classic, jump, natural, motivity, power, lut, noaccel }; [JsonConverter(Converters::StringEnumConverter::typeid)] public enum class SpacedTableMode { off, binlog, linear }; [StructLayout(LayoutKind::Sequential)] public value struct SpacedTableArgs { [JsonIgnore] SpacedTableMode mode; [MarshalAs(UnmanagedType::U1)] bool transfer; [MarshalAs(UnmanagedType::U1)] unsigned char partitions; short num; double start; double stop; }; generic [StructLayout(LayoutKind::Sequential)] public value struct Vec2 { T x; T y; }; [StructLayout(LayoutKind::Sequential)] public value struct TableArgs { [JsonProperty("Whether points affect velocity (true) or sensitivity (false)")] [MarshalAs(UnmanagedType::U1)] bool velocity; [JsonIgnore] int length; [MarshalAs(UnmanagedType::ByValArray, SizeConst = ra::ARB_LUT_CAPACITY)] array>^ points; virtual bool Equals(Object^ ob) override { if (ob->GetType() == this->GetType()) { TableArgs^ other = (TableArgs^)ob; if (this->length != other->length) return false; if (this->points == other->points) return true; if (unsigned(length) >= ra::ARB_LUT_CAPACITY || points == nullptr || other->points == nullptr) { throw gcnew InteropException("invalid table args"); } for (int i = 0; i < length; i++) { if (points[i].x != other->points[i].x || points[i].y != other->points[i].y) return false; } return true; } else { return false; } } virtual int GetHashCode() override { return points->GetHashCode() ^ length.GetHashCode(); } }; [StructLayout(LayoutKind::Sequential)] public value struct AccelArgs { AccelMode mode; [MarshalAs(UnmanagedType::U1)] bool legacy; double offset; double cap; double accelClassic; double decayRate; double growthRate; double motivity; double power; double scale; double weight; double exponent; double limit; double midpoint; double smooth; [JsonProperty(Required = Required::Default)] SpacedTableArgs spacedTableArgs; TableArgs tableData; }; [StructLayout(LayoutKind::Sequential)] public value struct DomainArgs { Vec2 domainXY; double lpNorm; }; [JsonObject(ItemRequired = Required::Always)] [StructLayout(LayoutKind::Sequential, CharSet = CharSet::Unicode)] public ref struct DriverSettings { literal double WriteDelayMs = ra::WRITE_DELAY; literal String^ Key = "Driver settings"; [JsonProperty("Degrees of rotation")] double rotation; [JsonProperty("Degrees of angle snapping")] double snap; [JsonProperty("Use x as whole/combined accel")] [MarshalAs(UnmanagedType::U1)] bool combineMagnitudes; double dpi; [JsonIgnore] double minimumSpeed; [JsonProperty("Input Speed Cap")] double maximumSpeed; [JsonProperty("Accel parameters")] Vec2 args; [JsonProperty("Sensitivity multipliers")] Vec2 sensitivity; [JsonProperty("Negative directional multipliers")] Vec2 directionalMultipliers; [JsonProperty("Stretches domain for horizontal vs vertical inputs")] DomainArgs domainArgs; [JsonProperty("Stretches accel range for horizontal vs vertical inputs")] Vec2 rangeXY; [JsonProperty(Required = Required::Default)] double minimumTime; [JsonProperty(Required = Required::Default)] double maximumTime; [JsonProperty("Ignore devices with matching ID")] [MarshalAs(UnmanagedType::U1)] bool ignore; [JsonProperty("Device ID")] [MarshalAs(UnmanagedType::ByValTStr, SizeConst = ra::MAX_DEV_ID_LEN)] String^ deviceID = ""; bool ShouldSerializeminimumTime() { return minimumTime != ra::DEFAULT_TIME_MIN; } bool ShouldSerializemaximumTime() { return maximumTime != ra::DEFAULT_TIME_MAX; } DriverSettings() { Marshal::PtrToStructure(IntPtr(&default_settings), this); } private: [OnDeserialized] void OnDeserializedMethod(StreamingContext context) { args.x.tableData.length = args.x.tableData.points->Length; args.y.tableData.length = args.y.tableData.points->Length; array>::Resize(args.x.tableData.points, ra::ARB_LUT_CAPACITY); array>::Resize(args.y.tableData.points, ra::ARB_LUT_CAPACITY); } [OnSerializing] void OnSerializingMethod(StreamingContext context) { array>::Resize(args.x.tableData.points, args.x.tableData.length); array>::Resize(args.y.tableData.points, args.y.tableData.length); } [OnSerialized] void OnSerializedMethod(StreamingContext context) { array>::Resize(args.x.tableData.points, ra::ARB_LUT_CAPACITY); array>::Resize(args.y.tableData.points, ra::ARB_LUT_CAPACITY); } }; [JsonObject(ItemRequired = Required::Always)] public ref struct LutBase { [JsonConverter(Converters::StringEnumConverter::typeid)] enum class Mode { logarithmic, linear } mode; virtual void SetArgs(AccelArgs%) {} virtual void SetData(ra::accel_union&) {} }; [JsonObject(ItemRequired = Required::Always)] public ref struct SpacedLut abstract : public LutBase { [JsonProperty("Whether points affect velocity (true) or sensitivity (false)")] bool transfer; double start; double stop; array^ data; void SetArgsBase(AccelArgs% args) { args.spacedTableArgs.transfer = transfer; args.spacedTableArgs.start = start; args.spacedTableArgs.stop = stop; } void SetDataBase(ra::accel_union& accel) { if (size_t(data->LongLength) > ra::SPACED_LUT_CAPACITY) { throw gcnew InteropException("data is too large"); } } }; [JsonObject(ItemRequired = Required::Always)] public ref struct LinearLut sealed : public SpacedLut { LinearLut() { } LinearLut(const ra::linear_lut& table) { mode = Mode::linear; transfer = table.transfer; start = table.range.start; stop = table.range.stop; data = gcnew array(table.range.num); pin_ptr pdata = &data[0]; std::memcpy(pdata, &table.data, sizeof(float) * data->Length); } virtual void SetArgs(AccelArgs% args) override { SetArgsBase(args); args.spacedTableArgs.num = data->Length; args.spacedTableArgs.mode = SpacedTableMode::linear; } virtual void SetData(ra::accel_union& accel) override { SetDataBase(accel); pin_ptr pdata = &data[0]; std::memcpy(&accel.lin_lut.data, pdata, sizeof(float) * data->Length); } }; [JsonObject(ItemRequired = Required::Always)] public ref struct BinLogLut sealed : public SpacedLut { short num; BinLogLut() { } BinLogLut(const ra::binlog_lut& table) { mode = Mode::logarithmic; transfer = table.transfer; start = table.range.start; stop = table.range.stop; num = table.range.num; data = gcnew array(1 + num * (int(stop) - int(start))); pin_ptr pdata = &data[0]; std::memcpy(pdata, &table.data, sizeof(float) * data->Length); } virtual void SetArgs(AccelArgs% args) override { SetArgsBase(args); args.spacedTableArgs.num = num; args.spacedTableArgs.mode = SpacedTableMode::binlog; } virtual void SetData(ra::accel_union& accel) override { SetDataBase(accel); if (data->Length != 1 + num * (int(stop) - int(start))) { throw gcnew InteropException("size of data does not match args"); } pin_ptr pdata = &data[0]; std::memcpy(&accel.log_lut.data, pdata, sizeof(float) * data->Length); } }; public ref struct RaConvert { static DriverSettings^ Settings(String^ json) { return NonNullable(json); } static String^ Settings(DriverSettings^ settings) { JObject^ jObject = JObject::FromObject(settings); String^ modes = String::Join(" | ", Enum::GetNames(AccelMode::typeid)); jObject->AddFirst(gcnew JProperty("### Accel Modes ###", modes)); return jObject->ToString(Formatting::Indented); } static LutBase^ Table(String^ json) { JObject^ jObject = JObject::Parse(json); if ((Object^)jObject == nullptr) { throw gcnew JsonException("bad json"); } LutBase^ base = NonNullable(jObject); switch (base->mode) { case LutBase::Mode::logarithmic: return NonNullable(jObject); case LutBase::Mode::linear: return NonNullable(jObject); default: throw gcnew NotImplementedException(); } } static String^ Table(LutBase^ lut) { auto serializerSettings = gcnew JsonSerializerSettings(); return JsonConvert::SerializeObject( lut, lut->GetType(), Formatting::Indented, serializerSettings); }; generic static T NonNullable(String^ json) { T obj = JsonConvert::DeserializeObject(json); if (obj == nullptr) throw gcnew JsonException("invalid JSON"); return obj; } generic static T NonNullable(JObject^ jObject) { T obj = jObject->ToObject(); if (obj == nullptr) throw gcnew JsonException("invalid JSON"); return obj; } }; public ref struct ExtendedSettings { DriverSettings^ baseSettings; Vec2 tables; using JSON = String^; ExtendedSettings(DriverSettings^ driverSettings) : baseSettings(driverSettings) {} ExtendedSettings() : ExtendedSettings(gcnew DriverSettings()) {} ExtendedSettings(JSON settingsJson) : ExtendedSettings(settingsJson, nullptr, nullptr, false) {} ExtendedSettings(JSON settingsJson, JSON tableJson) : ExtendedSettings(settingsJson, tableJson, nullptr, false) {} ExtendedSettings(JSON settingsJson, JSON xTableJson, JSON yTableJson) : ExtendedSettings(settingsJson, xTableJson, yTableJson, true) {} private: ExtendedSettings(JSON settingsJson, JSON xTableJson, JSON yTableJson, bool byComponent) { if (settingsJson) { baseSettings = RaConvert::Settings(settingsJson); } else { baseSettings = gcnew DriverSettings(); } if (xTableJson || yTableJson) { baseSettings->combineMagnitudes = !byComponent; } if (xTableJson) { tables.x = RaConvert::Table(xTableJson); tables.x->SetArgs(baseSettings->args.x); } if (yTableJson) { if (Object::ReferenceEquals(yTableJson, xTableJson)) { tables.y = tables.x; } else { tables.y = RaConvert::Table(yTableJson); } tables.y->SetArgs(baseSettings->args.y); } } }; public ref class SettingsErrors { public: List^ list; int lastX; int lastY; delegate void MsgHandler(const char*); void Add(const char* msg) { list->Add(gcnew String(msg)); } SettingsErrors(ExtendedSettings^ settings) : SettingsErrors(settings->baseSettings) {} SettingsErrors(DriverSettings^ settings) { MsgHandler^ del = gcnew MsgHandler(this, &SettingsErrors::Add); GCHandle gch = GCHandle::Alloc(del); auto fp = static_cast( Marshal::GetFunctionPointerForDelegate(del).ToPointer()); ra::settings* args_ptr = new ra::settings(); Marshal::StructureToPtr(settings, (IntPtr)args_ptr, false); list = gcnew List(); auto [last_x, last_y, _] = ra::valid(*args_ptr, fp); lastX = last_x; lastY = last_y; gch.Free(); delete args_ptr; } bool Empty() { return list->Count == 0; } virtual String^ ToString() override { Text::StringBuilder^ sb = gcnew Text::StringBuilder(); for each (auto s in list->GetRange(0, lastX)) { sb->AppendFormat("x: {0}\n", s); } for each (auto s in list->GetRange(lastX, lastY - lastX)) { sb->AppendFormat("y: {0}\n", s); } for each (auto s in list->GetRange(lastY, list->Count - lastY)) { sb->AppendLine(s); } return sb->ToString(); } }; struct instance_t { ra::io_t data; vec2 inv; }; public ref class ManagedAccel { instance_t* const instance = new instance_t(); public: ManagedAccel() {} ManagedAccel(ExtendedSettings^ settings) { Settings = settings; } virtual ~ManagedAccel() { delete instance; } !ManagedAccel() { delete instance; } Tuple^ Accelerate(int x, int y, double time) { vec2d in_out_vec = { (double)x, (double)y }; instance->data.mod.modify(in_out_vec, instance->inv, time); return gcnew Tuple(in_out_vec.x, in_out_vec.y); } void Activate() { try { ra::write(instance->data); } catch (const ra::error& e) { throw gcnew InteropException(e); } } property ExtendedSettings^ Settings { ExtendedSettings^ get() { auto settings = gcnew ExtendedSettings(); Marshal::PtrToStructure(IntPtr(&instance->data.args), settings->baseSettings); settings->tables.x = extract(instance->data.args.argsv.x.spaced_args.mode, instance->data.mod.accel.x); settings->tables.y = extract(instance->data.args.argsv.y.spaced_args.mode, instance->data.mod.accel.y); return settings; } void set(ExtendedSettings^ val) { Marshal::StructureToPtr(val->baseSettings, IntPtr(&instance->data.args), false); instance->data.mod = { instance->data.args }; instance->inv = ra::invokers(instance->data.args); if (val->tables.x) { val->tables.x->SetData(instance->data.mod.accel.x); } if (val->tables.y) { val->tables.y->SetData(instance->data.mod.accel.y); } } } static ManagedAccel^ GetActive() { try { auto active = gcnew ManagedAccel(); ra::read(active->instance->data); active->instance->inv = ra::invokers(active->instance->data.args); return active; } catch (const ra::error& e) { throw gcnew InteropException(e); } } private: LutBase^ extract(ra::spaced_lut_mode mode, ra::accel_union& au) { switch (mode) { case ra::spaced_lut_mode::off: return nullptr; case ra::spaced_lut_mode::linear: return gcnew LinearLut(au.lin_lut); case ra::spaced_lut_mode::binlog: return gcnew BinLogLut(au.log_lut); default: throw gcnew NotImplementedException(); } } }; public ref struct VersionHelper { literal String^ VersionString = RA_VER_STRING; static Version^ ValidOrThrow() { try { ra::version_t v = ra::valid_version_or_throw(); return gcnew Version(v.major, v.minor, v.patch, 0); } catch (const ra::error& e) { throw gcnew InteropException(e); } } };