-
-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* create base implementation * ensure proper deserialization * ensure calling activate/deactivate doesn't crash * ensure extension loading/unloading * major refactoring - removed `Newtonsoft.Json` in favor of `System.Text.Json` - refactored `VendorExtension` to allow for testing - refactored file-backed vendor extensions - renamed `BuiltIn` to `Host` - implement basic vendor APIs - add tests * further refactor and add tests * fix failing test Co-authored-by: Ayane Satomi <[email protected]>
- Loading branch information
Showing
27 changed files
with
1,149 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,7 +14,7 @@ jobs: | |
- name: Report | ||
uses: dorny/[email protected] | ||
with: | ||
artifact: TestResults-${{ inputs.name }} | ||
artifact: TestResults-Vignette.Core.Tests | ||
name: Test Results | ||
path: "*.trx" | ||
reporter: dotnet-trx |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
// Copyright (c) The Vignette Authors | ||
// Licensed under GPL-3.0 (With SDK Exception). See LICENSE for details. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace Vignette.Core.Extensions | ||
{ | ||
public abstract class Extension : IExtension | ||
{ | ||
public abstract string Name { get; } | ||
public abstract string Author { get; } | ||
public abstract string Description { get; } | ||
public abstract string Identifier { get; } | ||
public abstract Version Version { get; } | ||
public bool Activated { get; private set; } | ||
protected ExtensionSystem ExtensionSystem { get; private set; } | ||
protected IReadOnlyDictionary<string, object> Channels => channels; | ||
private readonly Dictionary<string, object> channels = new Dictionary<string, object>(); | ||
|
||
public void Activate(ExtensionSystem extensionSystem) | ||
{ | ||
if (Activated) | ||
return; | ||
|
||
ExtensionSystem = extensionSystem ?? throw new ArgumentNullException(nameof(extensionSystem)); | ||
|
||
Initialize(); | ||
|
||
Activated = true; | ||
} | ||
|
||
public void Deactivate() | ||
{ | ||
if (!Activated) | ||
return; | ||
|
||
Destroy(); | ||
|
||
ExtensionSystem = null; | ||
Activated = false; | ||
} | ||
|
||
public virtual Task<bool> TryDispatchAsync(IExtension actor, string channel, out object value, CancellationToken token = default, params object[] args) | ||
{ | ||
if (!channels.TryGetValue(channel, out var item)) | ||
{ | ||
value = new ChannelNotFoundException(); | ||
return Task.FromResult(false); | ||
} | ||
|
||
try | ||
{ | ||
value = Invoke(item, args); | ||
return Task.FromResult(true); | ||
} | ||
catch (Exception e) | ||
{ | ||
value = e; | ||
return Task.FromResult(false); | ||
} | ||
} | ||
|
||
public bool TryDispatch(IExtension actor, string channel, out object value, params object[] args) | ||
=> TryDispatchAsync(actor, channel, out value, default, args).GetAwaiter().GetResult(); | ||
|
||
public async Task<object> DispatchAsync(IExtension actor, string channel, params object[] args) | ||
{ | ||
await TryDispatchAsync(actor, channel, out var value, default, args); | ||
|
||
if (value is Exception e) | ||
throw e; | ||
|
||
return value; | ||
} | ||
|
||
public object Dispatch(IExtension actor, string channel, params object[] args) | ||
=> DispatchAsync(actor, channel, args).GetAwaiter().GetResult(); | ||
|
||
public async Task<T> DispatchAsync<T>(IExtension actor, string channel, params object[] args) | ||
=> (T)await DispatchAsync(actor, channel, args); | ||
|
||
public T Dispatch<T>(IExtension actor, string channel, params object[] args) | ||
=> (T)Dispatch(actor, channel, args); | ||
|
||
protected virtual void Initialize() | ||
{ | ||
} | ||
|
||
protected virtual void Destroy() | ||
{ | ||
} | ||
|
||
protected bool Register(string channel, object action) => channels.TryAdd(channel, action); | ||
protected void Unregister(string channel) => channels.Remove(channel); | ||
protected abstract object Invoke(object method, params object[] args); | ||
|
||
public bool Equals(IExtension other) | ||
=> Name.Equals(other.Name) && Author.Equals(other.Author) | ||
&& Description.Equals(other.Description) && Identifier.Equals(other.Identifier) | ||
&& Version.Equals(other.Version); | ||
|
||
public override bool Equals(object obj) | ||
=> Equals(obj as IExtension); | ||
|
||
public override int GetHashCode() | ||
=> HashCode.Combine(Name, Author, Description, Identifier, Version); | ||
|
||
public override string ToString() => $@"{Identifier} ({Version.ToString(3)})"; | ||
} | ||
|
||
public class ChannelNotFoundException : Exception | ||
{ | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// Copyright (c) The Vignette Authors | ||
// Licensed under GPL-3.0 (With SDK Exception). See LICENSE for details. | ||
|
||
using System; | ||
|
||
namespace Vignette.Core.Extensions | ||
{ | ||
[Flags] | ||
public enum ExtensionIntents | ||
{ | ||
None, | ||
Files, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Copyright (c) The Vignette Authors | ||
// Licensed under GPL-3.0 (With SDK Exception). See LICENSE for details. | ||
|
||
namespace Vignette.Core.Extensions.Vendor | ||
{ | ||
public enum ExtensionMode | ||
{ | ||
Production, | ||
Development, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
// Copyright (c) The Vignette Authors | ||
// Licensed under GPL-3.0 (With SDK Exception). See LICENSE for details. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using Microsoft.ClearScript.V8; | ||
using Stride.Core; | ||
using Stride.Core.Annotations; | ||
using Stride.Games; | ||
using Vignette.Core.Extensions.Host; | ||
using Vignette.Core.Extensions.Vendor; | ||
|
||
namespace Vignette.Core.Extensions | ||
{ | ||
public class ExtensionSystem : GameSystemBase | ||
{ | ||
public readonly V8Runtime Runtime = new V8Runtime(); | ||
public IReadOnlyCollection<IExtension> Loaded => extensions; | ||
private readonly HashSet<Extension> extensions = new HashSet<Extension>(); | ||
|
||
public ExtensionSystem([NotNull] IServiceRegistry registry) | ||
: base(registry) | ||
{ | ||
} | ||
|
||
public void Load(Extension extension) | ||
{ | ||
if (extension == null) | ||
throw new ArgumentNullException(nameof(extension)); | ||
|
||
if (Loaded.Contains(extension, EqualityComparer<IExtension>.Default)) | ||
throw new ExtensionLoadException(@"Extension is already loaded."); | ||
|
||
if (extension is VendorExtension vendored) | ||
{ | ||
var missing = vendored.Dependencies?.Where(dep => !Loaded.Any(ext => ext.Identifier == dep.Identifier && ext.Version >= dep.Version)); | ||
|
||
if (missing.Any()) | ||
throw new ExtensionLoadException(@"Failed to load extension as one or more dependencies have not been met."); | ||
} | ||
|
||
if (extensions.Add(extension)) | ||
extension.Activate(this); | ||
} | ||
|
||
public void Unload(Extension extension) | ||
{ | ||
if (extension == null) | ||
throw new ArgumentNullException(nameof(extension)); | ||
|
||
if (!Loaded.Contains(extension, EqualityComparer<IExtension>.Default)) | ||
throw new ExtensionUnloadException(@"Extension is not loaded."); | ||
|
||
if (extension is VendorExtension vendored) | ||
{ | ||
var allDependencies = Loaded.OfType<VendorExtension>().SelectMany(ext => ext.Dependencies).Distinct(EqualityComparer<VendorExtensionDependency>.Default); | ||
if (allDependencies.Any(dep => extension.Identifier == dep.Identifier && extension.Version >= dep.Version)) | ||
throw new ExtensionUnloadException(@"Failed to unload extension as one or more extensions depend on it."); | ||
} | ||
|
||
if (extensions.Remove(extension) && extension is not HostExtension) | ||
extension.Deactivate(); | ||
} | ||
|
||
protected override void Destroy() | ||
{ | ||
base.Destroy(); | ||
|
||
foreach (var ext in Loaded.OfType<VendorExtension>()) | ||
ext.Dispose(); | ||
|
||
Runtime.Dispose(); | ||
} | ||
} | ||
|
||
public class ExtensionLoadException : Exception | ||
{ | ||
public ExtensionLoadException(string message) | ||
: base(message) | ||
{ | ||
} | ||
} | ||
|
||
public class ExtensionUnloadException : Exception | ||
{ | ||
public ExtensionUnloadException(string message) | ||
: base(message) | ||
{ | ||
} | ||
} | ||
} |
Oops, something went wrong.