2019-09-03 16:57:34 +08:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2019-01-24 16:43:03 +08:00
// See the LICENCE file in the repository root for full licence text.
2018-04-13 17:19:50 +08:00
using System ;
2021-05-10 21:43:48 +08:00
using System.Collections.Generic ;
2021-10-22 13:41:59 +08:00
using System.IO ;
2021-05-10 21:43:48 +08:00
using System.Linq ;
using System.Text ;
2021-10-22 13:41:59 +08:00
using JetBrains.Annotations ;
2021-05-10 21:43:48 +08:00
using Newtonsoft.Json ;
2018-04-13 17:19:50 +08:00
using osu.Framework.Audio.Sample ;
2019-09-03 16:57:34 +08:00
using osu.Framework.Bindables ;
2018-04-13 17:19:50 +08:00
using osu.Framework.Graphics ;
2020-07-17 15:54:30 +08:00
using osu.Framework.Graphics.OpenGL.Textures ;
2018-04-13 17:19:50 +08:00
using osu.Framework.Graphics.Textures ;
2022-03-22 18:07:05 +08:00
using osu.Framework.IO.Stores ;
2021-10-01 21:15:10 +08:00
using osu.Framework.Logging ;
2019-08-23 19:32:43 +08:00
using osu.Game.Audio ;
2021-11-29 17:02:09 +08:00
using osu.Game.Database ;
2021-05-10 21:43:48 +08:00
using osu.Game.IO ;
using osu.Game.Screens.Play.HUD ;
2018-04-13 17:19:50 +08:00
namespace osu.Game.Skinning
{
2019-04-25 16:36:17 +08:00
public abstract class Skin : IDisposable , ISkin
2018-04-13 17:19:50 +08:00
{
2022-03-22 17:36:42 +08:00
/// <summary>
/// A texture store which can be used to perform user file loops for this skin.
/// </summary>
[CanBeNull]
protected TextureStore Textures { get ; set ; }
/// <summary>
/// A sample store which can be used to perform user file loops for this skin.
/// </summary>
[CanBeNull]
protected ISampleStore Samples { get ; set ; }
2022-01-26 12:37:33 +08:00
public readonly Live < SkinInfo > SkinInfo ;
2018-04-13 17:19:50 +08:00
2021-10-22 13:41:59 +08:00
public SkinConfiguration Configuration { get ; set ; }
2018-04-13 17:19:50 +08:00
2021-05-10 21:43:48 +08:00
public IDictionary < SkinnableTarget , SkinnableInfo [ ] > DrawableComponentInfo = > drawableComponentInfo ;
private readonly Dictionary < SkinnableTarget , SkinnableInfo [ ] > drawableComponentInfo = new Dictionary < SkinnableTarget , SkinnableInfo [ ] > ( ) ;
2018-04-13 17:19:50 +08:00
2021-02-18 17:32:28 +08:00
public abstract ISample GetSample ( ISampleInfo sampleInfo ) ;
2018-04-13 17:19:50 +08:00
2020-07-17 15:54:30 +08:00
public Texture GetTexture ( string componentName ) = > GetTexture ( componentName , default , default ) ;
public abstract Texture GetTexture ( string componentName , WrapMode wrapModeS , WrapMode wrapModeT ) ;
2018-04-13 17:19:50 +08:00
2019-09-03 16:57:34 +08:00
public abstract IBindable < TValue > GetConfig < TLookup , TValue > ( TLookup lookup ) ;
2018-04-13 17:19:50 +08:00
2022-03-23 12:14:56 +08:00
/// <summary>
/// Construct a new skin.
/// </summary>
/// <param name="skin">The skin's metadata. Usually a live realm object.</param>
/// <param name="resources">Access to game-wide resources.</param>
/// <param name="storage">An optional store which will be used for looking up skin resources. If null, one will be created from realm <see cref="IHasRealmFiles"/> pattern.</param>
/// <param name="configurationStream">An optional stream which will be used to read the skin configuration. If null, the configuration will be retrieved from the storage using "skin.ini".</param>
2022-03-22 18:07:05 +08:00
protected Skin ( SkinInfo skin , [ CanBeNull ] IStorageResourceProvider resources , [ CanBeNull ] IResourceStore < byte [ ] > storage = null , [ CanBeNull ] Stream configurationStream = null )
2018-04-13 17:19:50 +08:00
{
2022-03-22 18:07:05 +08:00
if ( resources ! = null )
2022-03-22 17:36:42 +08:00
{
2022-03-23 13:21:35 +08:00
SkinInfo = resources . RealmAccess ! = null
? skin . ToLive ( resources . RealmAccess )
: skin . ToLiveUnmanaged ( ) ;
storage ? ? = new RealmBackedResourceStore ( skin , resources . Files , new [ ] { @"ogg" } ) ;
var samples = resources . AudioManager ? . GetSampleStore ( storage ) ;
if ( samples ! = null )
samples . PlaybackConcurrency = OsuGameBase . SAMPLE_CONCURRENCY ;
Samples = samples ;
Textures = new TextureStore ( resources . CreateTextureLoaderStore ( storage ) ) ;
2022-03-22 17:36:42 +08:00
}
2022-03-22 18:07:05 +08:00
configurationStream ? ? = storage ? . GetStream ( @"skin.ini" ) ;
2021-10-22 13:41:59 +08:00
if ( configurationStream ! = null )
2021-10-24 22:43:37 +08:00
// stream will be closed after use by LineBufferedReader.
2021-10-22 13:41:59 +08:00
ParseConfigurationStream ( configurationStream ) ;
else
Configuration = new SkinConfiguration ( ) ;
2021-05-10 21:43:48 +08:00
2021-11-29 17:02:09 +08:00
// skininfo files may be null for default skin.
2022-03-22 17:36:42 +08:00
foreach ( SkinnableTarget skinnableTarget in Enum . GetValues ( typeof ( SkinnableTarget ) ) )
2021-05-11 10:54:45 +08:00
{
2022-03-22 17:36:42 +08:00
string filename = $"{skinnableTarget}.json" ;
2021-05-11 10:54:45 +08:00
2022-03-22 18:07:05 +08:00
byte [ ] bytes = storage ? . Get ( filename ) ;
2021-05-11 10:54:45 +08:00
2022-03-22 17:36:42 +08:00
if ( bytes = = null )
continue ;
2021-05-11 10:54:45 +08:00
2022-03-22 17:36:42 +08:00
try
{
string jsonContent = Encoding . UTF8 . GetString ( bytes ) ;
var deserializedContent = JsonConvert . DeserializeObject < IEnumerable < SkinnableInfo > > ( jsonContent ) ;
2021-05-11 10:54:45 +08:00
2022-03-22 17:36:42 +08:00
if ( deserializedContent = = null )
2021-11-29 17:02:09 +08:00
continue ;
2021-05-11 10:54:45 +08:00
2022-03-22 17:36:42 +08:00
DrawableComponentInfo [ skinnableTarget ] = deserializedContent . ToArray ( ) ;
}
catch ( Exception ex )
{
Logger . Error ( ex , "Failed to load skin configuration." ) ;
2021-10-01 21:15:10 +08:00
}
2022-03-22 17:36:42 +08:00
}
2021-05-11 10:54:45 +08:00
}
2021-10-22 13:41:59 +08:00
protected virtual void ParseConfigurationStream ( Stream stream )
{
using ( LineBufferedReader reader = new LineBufferedReader ( stream , true ) )
Configuration = new LegacySkinDecoder ( ) . Decode ( reader ) ;
}
2021-05-13 12:09:33 +08:00
/// <summary>
/// Remove all stored customisations for the provided target.
/// </summary>
/// <param name="targetContainer">The target container to reset.</param>
2021-05-13 16:25:51 +08:00
public void ResetDrawableTarget ( ISkinnableTarget targetContainer )
2021-05-11 10:54:45 +08:00
{
DrawableComponentInfo . Remove ( targetContainer . Target ) ;
2021-05-10 21:43:48 +08:00
}
2021-05-13 12:09:33 +08:00
/// <summary>
/// Update serialised information for the provided target.
/// </summary>
/// <param name="targetContainer">The target container to serialise to this skin.</param>
2021-05-13 16:25:51 +08:00
public void UpdateDrawableTarget ( ISkinnableTarget targetContainer )
2021-05-10 21:43:48 +08:00
{
2021-05-13 12:14:49 +08:00
DrawableComponentInfo [ targetContainer . Target ] = targetContainer . CreateSkinnableInfo ( ) . ToArray ( ) ;
2021-05-10 21:43:48 +08:00
}
public virtual Drawable GetDrawableComponent ( ISkinComponent component )
{
switch ( component )
{
case SkinnableTargetComponent target :
2021-05-13 12:09:33 +08:00
if ( ! DrawableComponentInfo . TryGetValue ( target . Target , out var skinnableInfo ) )
2021-05-10 21:43:48 +08:00
return null ;
2021-05-13 17:51:23 +08:00
return new SkinnableTargetComponentsContainer
2021-05-10 21:43:48 +08:00
{
ChildrenEnumerable = skinnableInfo . Select ( i = > i . CreateInstance ( ) )
} ;
}
return null ;
2018-04-13 17:19:50 +08:00
}
#region Disposal
~ Skin ( )
{
2021-03-02 15:07:51 +08:00
// required to potentially clean up sample store from audio hierarchy.
2018-04-13 17:19:50 +08:00
Dispose ( false ) ;
}
public void Dispose ( )
{
Dispose ( true ) ;
GC . SuppressFinalize ( this ) ;
}
private bool isDisposed ;
protected virtual void Dispose ( bool isDisposing )
{
if ( isDisposed )
return ;
2019-02-28 12:31:40 +08:00
2018-04-13 17:19:50 +08:00
isDisposed = true ;
2022-03-22 17:36:42 +08:00
Textures ? . Dispose ( ) ;
Samples ? . Dispose ( ) ;
2018-04-13 17:19:50 +08:00
}
#endregion
}
}