mirror of
https://github.com/Ellpeck/MLEM.git
synced 2024-11-24 21:48:35 +01:00
Compare commits
5 commits
778d416774
...
a9e243835f
Author | SHA1 | Date | |
---|---|---|---|
a9e243835f | |||
b22d2d4d22 | |||
39138446ea | |||
ae559adf26 | |||
8e83cc06a6 |
12 changed files with 177 additions and 35 deletions
|
@ -38,6 +38,7 @@ Fixes
|
|||
### MLEM.Data
|
||||
Additions
|
||||
- Allow RuntimeTexturePacker to automatically dispose submitted textures when packing
|
||||
- Added JsonTypeSafeWrapper and JsonTypeSafeGenericDataHolder
|
||||
|
||||
Improvements
|
||||
- Use TitleContainer for opening streams where possible
|
||||
|
|
10
Jenkinsfile
vendored
10
Jenkinsfile
vendored
|
@ -9,12 +9,16 @@ pipeline {
|
|||
}
|
||||
}
|
||||
stage('Document') {
|
||||
steps {
|
||||
sh 'dotnet cake --target Document --branch ' + env.BRANCH_NAME
|
||||
}
|
||||
}
|
||||
stage('Publish Docs') {
|
||||
when {
|
||||
branch 'release'
|
||||
branch 'release'
|
||||
}
|
||||
steps {
|
||||
sh 'dotnet cake --target Document'
|
||||
sh 'cp Docs/_site/** /var/www/MLEM/ -r'
|
||||
sh 'cp Docs/_site/** /var/www/MLEM/ -r'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,8 +30,8 @@ namespace MLEM.Data {
|
|||
/// </example>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To see a Data Texture Atlas in action, you can check out the sandbox project: <see href="https://github.com/Ellpeck/MLEM/blob/main/Sandbox/Content/Textures/Furniture.atlas"/>.
|
||||
/// Additionally, if you are using Aseprite, there is a script to automatically populate it: <see href="https://github.com/Ellpeck/MLEM/blob/main/Utilities/Populate%20Data%20Texture%20Atlas.lua"/>.
|
||||
/// To see a Data Texture Atlas in action, you can check out the sandbox project: https://github.com/Ellpeck/MLEM/blob/main/Sandbox/Content/Textures/Furniture.atlas.
|
||||
/// Additionally, if you are using Aseprite, there is a script to automatically populate it: https://github.com/Ellpeck/MLEM/blob/main/Utilities/Populate%20Data%20Texture%20Atlas.lua.
|
||||
/// </remarks>
|
||||
public class DataTextureAtlas {
|
||||
|
||||
|
|
45
MLEM.Data/Json/JsonTypeSafeGenericDataHolder.cs
Normal file
45
MLEM.Data/Json/JsonTypeSafeGenericDataHolder.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using MLEM.Misc;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace MLEM.Data.Json {
|
||||
/// <summary>
|
||||
/// An <see cref="IGenericDataHolder"/> represents an object that can hold generic key-value based data.
|
||||
/// This class uses <see cref="JsonTypeSafeWrapper"/> for each object stored to ensure that objects with a custom <see cref="JsonConverter"/> get deserialized as an instance of their original type if <see cref="JsonSerializer.TypeNameHandling"/> is not set to <see cref="TypeNameHandling.None"/>.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class JsonTypeSafeGenericDataHolder : IGenericDataHolder {
|
||||
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
private Dictionary<string, JsonTypeSafeWrapper> data;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetData(string key, object data) {
|
||||
if (data == default) {
|
||||
if (this.data != null)
|
||||
this.data.Remove(key);
|
||||
} else {
|
||||
if (this.data == null)
|
||||
this.data = new Dictionary<string, JsonTypeSafeWrapper>();
|
||||
this.data[key] = JsonTypeSafeWrapper.Of(data);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetData<T>(string key) {
|
||||
if (this.data != null && this.data.TryGetValue(key, out var val))
|
||||
return (T) val.Value;
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyCollection<string> GetDataKeys() {
|
||||
if (this.data == null)
|
||||
return Array.Empty<string>();
|
||||
return this.data.Keys;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
63
MLEM.Data/Json/JsonTypeSafeWrapper.cs
Normal file
63
MLEM.Data/Json/JsonTypeSafeWrapper.cs
Normal file
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace MLEM.Data.Json {
|
||||
/// <summary>
|
||||
/// A json type-safe wrapper can be used to wrap any objects that have a custom <see cref="JsonConverter"/> which stores them as a primitive type and that are serialized using a <see cref="JsonSerializer"/> in cases where <see cref="JsonSerializer.TypeNameHandling"/> is not set to <see cref="TypeNameHandling.None"/>.
|
||||
/// If these objects are not wrapped in this manner, the value deserialized from it might not have the same type as the originally serialized object. This behavior can be observed, for example, when serializing a <see cref="List{T}"/> of <see cref="object"/> entries, one of which is a <see cref="TimeSpan"/>: The <see cref="TimeSpan"/> will be serialized as a <see cref="string"/> and, upon deserialization, will remain a <see cref="string"/>.
|
||||
/// In general, wrapping objects in this manner is only useful in rare cases, where custom data of an unexpected or unknown type is stored.
|
||||
/// See <see cref="JsonTypeSafeGenericDataHolder"/> for an example of how this class can be used, and see this stackoverflow answer for more information on the problem that this class solves: https://stackoverflow.com/a/38798114.
|
||||
/// </summary>
|
||||
public abstract class JsonTypeSafeWrapper {
|
||||
|
||||
/// <summary>
|
||||
/// Returns this json type-safe wrapper's value as an <see cref="object"/>.
|
||||
/// </summary>
|
||||
public abstract object Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current <see cref="Value"/> of this <see cref="JsonTypeSafeWrapper"/>, typecast to the given type <typeparamref name="T"/>.
|
||||
/// If this <see cref="Value"/>'s type is incompatible with the given type, the type's default value is returned instead.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value to return</typeparam>
|
||||
/// <returns>The <see cref="Value"/>, castt to the given type if compatible, otherwise default</returns>
|
||||
public T GetValue<T>() {
|
||||
return this.Value is T t ? t : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="JsonTypeSafeWrapper{T}"/> from the given value.
|
||||
/// The type parameter of the returned wrapper will be equal to the <see cref="Type"/> of the <paramref name="value"/> passed.
|
||||
/// If a <see cref="JsonTypeSafeWrapper{T}"/> for a specific type, known at comepile type, should be created, you can use <see cref="JsonTypeSafeWrapper{T}(T)"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to wrap</param>
|
||||
/// <returns>A <see cref="JsonTypeSafeWrapper{T}"/> with a type matching the type of <paramref name="value"/></returns>
|
||||
public static JsonTypeSafeWrapper Of(object value) {
|
||||
var type = typeof(JsonTypeSafeWrapper<>).MakeGenericType(value.GetType());
|
||||
return (JsonTypeSafeWrapper) Activator.CreateInstance(type, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataContract]
|
||||
public class JsonTypeSafeWrapper<T> : JsonTypeSafeWrapper {
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object Value => this.value;
|
||||
[DataMember]
|
||||
private readonly T value;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new json type-safe wrapper instance that wraps the given <paramref name="value"/>.
|
||||
/// If the type of the value is unknown at compile time, <see cref="JsonTypeSafeWrapper.Of"/> can be used instead.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to wrap</param>
|
||||
public JsonTypeSafeWrapper(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ namespace MLEM.Ui.Style {
|
|||
/// <summary>
|
||||
/// The style settings for a <see cref="UiSystem"/>.
|
||||
/// Each <see cref="Element"/> uses these style settings by default, however you can also change these settings per element using the elements' individual style settings.
|
||||
/// Note that this class is a <see cref="GenericDataHolder"/>, meaning additional styles for custom components can easily be added using <see cref="GenericDataHolder.SetData"/>
|
||||
/// Note that this class is a <see cref="GenericDataHolder"/>, meaning additional styles for custom components can easily be added using <see cref="GenericDataHolder.SetData{T}"/>
|
||||
/// </summary>
|
||||
public class UiStyle : GenericDataHolder {
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace MLEM.Ui {
|
|||
/// <summary>
|
||||
/// A ui system is the central location for the updating and rendering of all ui <see cref="Element"/>s.
|
||||
/// Each element added to the root of the ui system is assigned a <see cref="RootElement"/> that has additional data like a transformation matrix.
|
||||
/// For more information on how ui systems work, check out <see href="https://mlem.ellpeck.de/articles/ui.html"/>
|
||||
/// For more information on how ui systems work, check out https://mlem.ellpeck.de/articles/ui.html.
|
||||
/// </summary>
|
||||
public class UiSystem : GameComponent {
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace MLEM.Misc {
|
|||
/// This allows, for example, a grass patch on a tilemap to have nice looking edges that transfer over into a path without any hard edges between tiles.
|
||||
///
|
||||
/// For auto-tiling in this way to work, the tiles have to be laid out in a specific order. This order is shown in the auto-tiling demo's textures.
|
||||
/// For more information and an example, see <see href="https://github.com/Ellpeck/MLEM/blob/main/Demos/AutoTilingDemo.cs#L20-L28"/>
|
||||
/// For more information and an example, see https://github.com/Ellpeck/MLEM/blob/main/Demos/AutoTilingDemo.cs#L20-L28.
|
||||
/// </summary>
|
||||
/// <param name="batch"></param>
|
||||
/// <param name="pos"></param>
|
||||
|
|
|
@ -2,64 +2,64 @@ using System;
|
|||
|
||||
namespace MLEM.Misc {
|
||||
/// <summary>
|
||||
/// This class contains a set of easing functions, adapted from <see href="https://easings.net"/>.
|
||||
/// This class contains a set of easing functions, adapted from https://easings.net.
|
||||
/// These can be used for ui elements or any other kind of animations.
|
||||
/// By default, each function takes an input that ranges between 0 and 1, and produces an output that roughly ranges between 0 and 1. To change this behavior, you can use <see cref="ScaleInput"/> and <see cref="ScaleOutput"/>.
|
||||
/// </summary>
|
||||
public static class Easings {
|
||||
|
||||
/// <summary><see href="https://easings.net/#easeInSine"/></summary>
|
||||
/// <summary>https://easings.net/#easeInSine</summary>
|
||||
public static readonly Easing InSine = p => 1 - (float) Math.Cos(p * Math.PI / 2);
|
||||
/// <summary><see href="https://easings.net/#easeOutSine"/></summary>
|
||||
/// <summary>https://easings.net/#easeOutSine</summary>
|
||||
public static readonly Easing OutSine = p => (float) Math.Sin(p * Math.PI / 2);
|
||||
/// <summary><see href="https://easings.net/#easeInOutSine"/></summary>
|
||||
/// <summary>https://easings.net/#easeInOutSine</summary>
|
||||
public static readonly Easing InOutSine = p => -((float) Math.Cos(p * Math.PI) - 1) / 2;
|
||||
|
||||
/// <summary><see href="https://easings.net/#easeInQuad"/></summary>
|
||||
/// <summary>https://easings.net/#easeInQuad</summary>
|
||||
public static readonly Easing InQuad = p => p * p;
|
||||
/// <summary><see href="https://easings.net/#easeOutQuad"/></summary>
|
||||
/// <summary>https://easings.net/#easeOutQuad</summary>
|
||||
public static readonly Easing OutQuad = p => 1 - (1 - p) * (1 - p);
|
||||
/// <summary><see href="https://easings.net/#easeInOutQuad"/></summary>
|
||||
/// <summary>https://easings.net/#easeInOutQuad</summary>
|
||||
public static readonly Easing InOutQuad = p => p < 0.5F ? 2 * p * p : 1 - (-2 * p + 2) * (-2 * p + 2) / 2;
|
||||
|
||||
/// <summary><see href="https://easings.net/#easeInCubic"/></summary>
|
||||
/// <summary>https://easings.net/#easeInCubic</summary>
|
||||
public static readonly Easing InCubic = p => p * p * p;
|
||||
/// <summary><see href="https://easings.net/#easeOutCubic"/></summary>
|
||||
/// <summary>https://easings.net/#easeOutCubic</summary>
|
||||
public static readonly Easing OutCubic = p => 1 - (1 - p) * (1 - p) * (1 - p);
|
||||
/// <summary><see href="https://easings.net/#easeInOutCubic"/></summary>
|
||||
/// <summary>https://easings.net/#easeInOutCubic</summary>
|
||||
public static readonly Easing InOutCubic = p => p < 0.5F ? 4 * p * p * p : 1 - (-2 * p + 2) * (-2 * p + 2) * (-2 * p + 2) / 2;
|
||||
|
||||
/// <summary><see href="https://easings.net/#easeInExpo"/></summary>
|
||||
/// <summary>https://easings.net/#easeInExpo</summary>
|
||||
public static readonly Easing InExpo = p => p == 0 ? 0 : (float) Math.Pow(2, 10 * p - 10);
|
||||
/// <summary><see href="https://easings.net/#easeOutExpo"/></summary>
|
||||
/// <summary>https://easings.net/#easeOutExpo</summary>
|
||||
public static readonly Easing OutExpo = p => p == 1 ? 1 : 1 - (float) Math.Pow(2, -10 * p);
|
||||
/// <summary><see href="https://easings.net/#easeInOutExpo"/></summary>
|
||||
/// <summary>https://easings.net/#easeInOutExpo</summary>
|
||||
public static readonly Easing InOutExpo = p => p == 0 ? 0 : p == 1 ? 1 : p < 0.5F ? (float) Math.Pow(2, 20 * p - 10) / 2 : (2 - (float) Math.Pow(2, -20 * p + 10)) / 2;
|
||||
|
||||
/// <summary><see href="https://easings.net/#easeInCirc"/></summary>
|
||||
/// <summary>https://easings.net/#easeInCirc</summary>
|
||||
public static readonly Easing InCirc = p => 1 - (float) Math.Sqrt(1 - p * p);
|
||||
/// <summary><see href="https://easings.net/#easeOutCirc"/></summary>
|
||||
/// <summary>https://easings.net/#easeOutCirc</summary>
|
||||
public static readonly Easing OutCirc = p => (float) Math.Sqrt(1 - (p - 1) * (p - 1));
|
||||
/// <summary><see href="https://easings.net/#easeInOutCirc"/></summary>
|
||||
/// <summary>https://easings.net/#easeInOutCirc</summary>
|
||||
public static readonly Easing InOutCirc = p => p < 0.5F ? (1 - (float) Math.Sqrt(1 - 2 * p * (2 * p))) / 2 : ((float) Math.Sqrt(1 - (-2 * p + 2) * (-2 * p + 2)) + 1) / 2;
|
||||
|
||||
/// <summary><see href="https://easings.net/#easeInBack"/></summary>
|
||||
/// <summary>https://easings.net/#easeInBack</summary>
|
||||
public static readonly Easing InBack = p => 2.70158F * p * p * p - 1.70158F * p * p;
|
||||
/// <summary><see href="https://easings.net/#easeOutBack"/></summary>
|
||||
/// <summary>https://easings.net/#easeOutBack</summary>
|
||||
public static readonly Easing OutBack = p => 1 + 2.70158F * (p - 1) * (p - 1) * (p - 1) + 1.70158F * (p - 1) * (p - 1);
|
||||
/// <summary><see href="https://easings.net/#easeInOutBack"/></summary>
|
||||
/// <summary>https://easings.net/#easeInOutBack</summary>
|
||||
public static readonly Easing InOutBack = p => p < 0.5 ? 2 * p * (2 * p) * ((2.594909F + 1) * 2 * p - 2.594909F) / 2 : ((2 * p - 2) * (2 * p - 2) * ((2.594909F + 1) * (p * 2 - 2) + 2.594909F) + 2) / 2;
|
||||
|
||||
/// <summary><see href="https://easings.net/#easeInElastic"/></summary>
|
||||
/// <summary>https://easings.net/#easeInElastic</summary>
|
||||
public static readonly Easing InElastic = p => p == 0 ? 0 : p == 1 ? 1 : -(float) Math.Pow(2, 10 * p - 10) * (float) Math.Sin((p * 10 - 10.75) * 2.094395F);
|
||||
/// <summary><see href="https://easings.net/#easeOutElastic"/></summary>
|
||||
/// <summary>https://easings.net/#easeOutElastic</summary>
|
||||
public static readonly Easing OutElastic = p => p == 0 ? 0 : p == 1 ? 1 : (float) Math.Pow(2, -10 * p) * (float) Math.Sin((p * 10 - 0.75) * 2.0943951023932F) + 1;
|
||||
/// <summary><see href="https://easings.net/#easeInOutElastic"/></summary>
|
||||
/// <summary>https://easings.net/#easeInOutElastic</summary>
|
||||
public static readonly Easing InOutElastic = p => p == 0 ? 0 : p == 1 ? 1 : p < 0.5 ? -((float) Math.Pow(2, 20 * p - 10) * (float) Math.Sin((20 * p - 11.125) * 1.39626340159546F)) / 2 : (float) Math.Pow(2, -20 * p + 10) * (float) Math.Sin((20 * p - 11.125) * 1.39626340159546F) / 2 + 1;
|
||||
|
||||
/// <summary><see href="https://easings.net/#easeInBounce"/></summary>
|
||||
/// <summary>https://easings.net/#easeInBounce</summary>
|
||||
public static readonly Easing InBounce = p => 1 - OutBounce(1 - p);
|
||||
/// <summary><see href="https://easings.net/#easeOutBounce"/></summary>
|
||||
/// <summary>https://easings.net/#easeOutBounce</summary>
|
||||
public static readonly Easing OutBounce = p => {
|
||||
const float n1 = 7.5625F;
|
||||
const float d1 = 2.75F;
|
||||
|
@ -73,7 +73,7 @@ namespace MLEM.Misc {
|
|||
return n1 * (p -= 2.625F / d1) * p + 0.984375F;
|
||||
}
|
||||
};
|
||||
/// <summary><see href="https://easings.net/#easeInOutBounce"/></summary>
|
||||
/// <summary>https://easings.net/#easeInOutBounce</summary>
|
||||
public static readonly Easing InOutBounce = p => p < 0.5 ? (1 - OutBounce(1 - 2 * p)) / 2 : (1 + OutBounce(2 * p - 1)) / 2;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -3,7 +3,11 @@ using System.Collections.Generic;
|
|||
using System.Runtime.Serialization;
|
||||
|
||||
namespace MLEM.Misc {
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Represents an object that can hold generic key-value based data.
|
||||
/// A lot of MLEM components extend this class to allow for users to add additional data to them easily.
|
||||
/// This <see cref="IGenericDataHolder"/> implemention uses an underlying <see cref="Dictionary{String,Object}"/> that only keeps track of non-default values.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class GenericDataHolder : IGenericDataHolder {
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace MLEM.Misc {
|
|||
/// <summary>
|
||||
/// MlemPlatform is a wrapper around some of MonoGame's platform-dependent behavior to allow for MLEM to stay platform-independent.
|
||||
/// See <see cref="DesktopGl{T}"/>, <see cref="Mobile"/> and <see cref="None"/> for information on the specific platforms.
|
||||
/// The MLEM demos' main classes also make use of this functionality: <see href="https://github.com/Ellpeck/MLEM/blob/main/Demos.DesktopGL/Program.cs#L8"/> and <see href="https://github.com/Ellpeck/MLEM/blob/main/Demos.Android/Activity1.cs#L33"/>.
|
||||
/// The MLEM demos' main classes also make use of this functionality: https://github.com/Ellpeck/MLEM/blob/main/Demos.DesktopGL/Program.cs#L8 and https://github.com/Ellpeck/MLEM/blob/main/Demos.Android/Activity1.cs#L33.
|
||||
/// </summary>
|
||||
public abstract class MlemPlatform {
|
||||
|
||||
|
|
|
@ -92,6 +92,31 @@ namespace Tests {
|
|||
Assert.AreEqual(Or(flags[24], flags[43]).ToString(), "Flag24 | Flag43");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestJsonTypeSafety() {
|
||||
var serializer = new JsonSerializer {TypeNameHandling = TypeNameHandling.Auto};
|
||||
|
||||
// normal generic data holder should crush the time span down to a string due to its custom serializer
|
||||
var data = new GenericDataHolder();
|
||||
data.SetData("Time", TimeSpan.FromMinutes(5));
|
||||
var read = SerializeAndDeserialize(serializer, data);
|
||||
Assert.IsNotInstanceOf<TimeSpan>(read.GetData<object>("Time"));
|
||||
Assert.Throws<InvalidCastException>(() => read.GetData<TimeSpan>("Time"));
|
||||
|
||||
// json type safe generic data holder should wrap the time span to ensure that it stays a time span
|
||||
var safeData = new JsonTypeSafeGenericDataHolder();
|
||||
safeData.SetData("Time", TimeSpan.FromMinutes(5));
|
||||
var safeRead = SerializeAndDeserialize(serializer, safeData);
|
||||
Assert.IsInstanceOf<TimeSpan>(safeRead.GetData<object>("Time"));
|
||||
Assert.DoesNotThrow(() => safeRead.GetData<TimeSpan>("Time"));
|
||||
}
|
||||
|
||||
private static T SerializeAndDeserialize<T>(JsonSerializer serializer, T t) {
|
||||
var writer = new StringWriter();
|
||||
serializer.Serialize(writer, t);
|
||||
return serializer.Deserialize<T>(new JsonTextReader(new StringReader(writer.ToString())));
|
||||
}
|
||||
|
||||
private class TestObject {
|
||||
|
||||
public Vector2 Vec;
|
||||
|
|
Loading…
Reference in a new issue