s&box Package Code Search
Search C# source code, UI razor templates, shaders, and configs across s&box packages.
link Raw Facepunch API Link: https://public.facepunch.com/sbox/code/search/1/?ident=coomzy.libraryimporter&take=20
Showing code results for query:
*
(16 total matches found)
Game
library
public sealed class JiggleBone : TransformProxyComponent
{
JiggleBoneState state = new JiggleBoneState();
[Property]
public Vector3 StartPoint = new Vector3( 0, 0, 0 );
[Property]
public Vector3 EndPoint = new Vector3( 32, 0, 0 );
[Property, Range( 0, 2 )]
public float Speed { get; set; } = 1.0f;
[Property, Range( 0, 2 )]
public float Stiffness { get; set; } = 1.0f;
[Property, Range( 0, 2 )]
public float Damping { get; set; } = 1.0f;
[Property, Range( 0, 100 )]
public float Radius { get; set; } = 40.0f;
[Property, Range( 0, 100 )]
public float Mass { get; set; } = 1.0f;
Transform LocalJigglePosition;
protected override void OnEnabled()
{
LocalJigglePosition = Transform.Local;
base.OnEnabled();
state = new JiggleBoneState();
}
protected override void OnUpdate()
{
var oldPos = LocalJigglePosition;
using ( Transform.DisableProxy() )
{
var worldTx = Transform.World;
var startPoint = worldTx.PointToWorld( StartPoint );
var endPoint = worldTx.PointToWorld( EndPoint );
//Gizmo.Draw.LineSphere( startPoint, 1 );
//Gizmo.Draw.LineSphere( endPoint, 1 );
state.Extent = (endPoint - startPoint);
state.Stiffness = Stiffness;
state.Damping = Damping;
state.Radius = Radius;
state.Mass = Mass;
state.Update( startPoint, Time.Delta * Speed * 16.0f );
var tx = worldTx.RotateAround( startPoint, state.Rotation );
LocalJigglePosition = GameObject.Parent.Transform.World.ToLocal( tx );
}
if ( oldPos != LocalJigglePosition )
{
MarkTransformChanged();
}
}
protected override void DrawGizmos()
{
base.DrawGizmos();
if ( !Gizmo.IsSelected )
return;
using ( Transform.DisableProxy() )
{
Gizmo.Transform = Transform.World;
Gizmo.Draw.IgnoreDepth = false;
Gizmo.Draw.Color = Gizmo.Colors.Yaw.WithAlpha( 0.5f );
Gizmo.Draw.Line( StartPoint, EndPoint );
Gizmo.Draw.LineBBox( BBox.FromPositionAndSize( StartPoint, 5 ) );
Gizmo.Draw.LineBBox( BBox.FromPositionAndSize( EndPoint, 5 ) );
Gizmo.Draw.LineSphere( EndPoint, Radius * 2.0f, 4 );
}
}
public override Transform GetLocalTransform()
{
return LocalJigglePosition;
}
}
class JiggleBoneState
{
public Vector3 Extent = new Vector3( 32, 0, 0 );
public Vector3 Position { get; set; }
public Rotation Rotation { get; set; }
public float Stiffness { get; set; } = 1.0f;
public float Damping { get; set; } = 1.0f;
public float Radius { get; set; } = 10.0f;
public float Gravity { get; set; } = 1.0f;
public float Mass { get; set; } = 1.0f;
Vector3 basePosition;
Vector3 velocity;
public JiggleBoneState()
{
}
internal void Update( Vector3 position, float timeDelta )
{
basePosition = position + Extent;
// initialization
if ( Position == default )
{
Position = basePosition;
}
// Calculate spring force based on displacement from the cube
Vector3 displacement = Position - basePosition;
Vector3 springForce = -Stiffness * displacement;
// Calculate acceleration (Newton's second law)
Vector3 acceleration = springForce / Mass;
// Update velocity (integrate acceleration)
velocity += acceleration * timeDelta;
// Apply exponential damping
velocity *= (float)Math.Exp( -Damping * timeDelta );
// Update position (integrate velocity)
Position += velocity * timeDelta;
{
var diff = Position - basePosition;
var diffLen = diff.Length;
if ( diffLen > Radius )
{
Position = basePosition + diff.Normal * Radius;
//velocity = velocity.AddClamped( -diff * 2.0f, diff.Length );
}
}
// Store the rotation offset result
Rotation = Rotation.FromToRotation( basePosition - position, Position - position );
//Gizmo.Draw.IgnoreDepth = true;
//Gizmo.Draw.Line( position, Position );
//Gizmo.Draw.Line( basePosition, Position );
}
}
Game
library
using Sandbox;
/// <summary>
/// This is a component - in your library!
/// </summary>
[Title( "LibraryImporter - My Component" )]
public class MyLibraryComponent : Component
{
}
UnitTest
library
using Sandbox;
[TestClass]
public partial class LibraryTests
{
[TestMethod]
public void SceneTest()
{
var scene = new Scene();
using ( scene.Push() )
{
var go = new GameObject();
Assert.AreEqual( 1, scene.Directory.GameObjectCount );
}
}
}
UnitTest
library
global using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class TestInit
{
[AssemblyInitialize]
public static void ClassInitialize( TestContext context )
{
Sandbox.Application.InitUnitTest();
}
}
Editor
library
using System;
using System.Threading.Tasks;
class LibraryListProxy : ListView
{
public Action<Package> OnLibrarySelected { get; set; }
public bool ShowInstalled { get; set; }
public bool ShowAvailable { get; set; }
public string Filter { get; set; } = "";
public LibraryListProxy( Widget parent ) : base( parent )
{
ItemContextMenu = ShowItemContext;
ItemSelected = OnItemClicked;
ItemSize = new Vector2( -1, 64 );
}
public void OnItemClicked( object value )
{
if ( value is LibraryProject lib )
{
OnLibrarySelected?.Invoke( lib.Project.Package );
}
if ( value is Package p )
{
OnLibrarySelected?.Invoke( p );
}
}
private void ShowItemContext( object obj )
{
LibraryProject project = obj as LibraryProject;
Package package = obj as Package ?? project?.Project.Package;
var m = new Menu();
if ( package is not null && package.Org.Ident != "local" )
{
m.AddOption( "View in Browser", "public", () => EditorUtility.OpenFolder( package.Url ) );
m.AddSeparator();
}
if ( project is not null )
{
m.AddOption( "Project Properties", "tune", () => _ = ProjectSettingsWindow.OpenForProject( project.Project ) );
m.AddSeparator();
m.AddOption( "Publish Project", "upload_file", () => _ = ProjectSettingsWindow.OpenForProject( project.Project, "upload" ) );
m.AddSeparator();
m.AddOption( "Show in Explorer", "folder", () => EditorUtility.OpenFolder( project.Project.RootDirectory.FullName ) );
}
m.OpenAt( Editor.Application.CursorPosition );
}
protected override void PaintItem( VirtualWidget item )
{
if ( item.Object is LibraryProject c )
PaintItem( item, c.Project.Package );
if ( item.Object is Package p )
PaintItem( item, p );
}
private void PaintItem( VirtualWidget item, Package c )
{
var rect = item.Rect;
if ( Paint.HasPressed )
rect = rect.Shrink( 2, 2, 0, 0 );
var library = LibrarySystem.All.Where( x => x.Project.Package.Ident == c.Ident && x.Project.Package.Org.Ident == c.Org.Ident && x.Project.Package.Ident != "libraryimporter").FirstOrDefault();
var pen = Color.White.WithAlpha( 0.6f );
if ( Paint.HasMouseOver || Paint.HasSelected )
{
Paint.SetBrush( Theme.Primary.WithAlpha( Paint.HasMouseOver ? 0.8f : 0.5f ) );
Paint.ClearPen();
Paint.DrawRect( rect, 4 );
pen = Color.White.WithAlpha( Paint.HasMouseOver ? 1 : 0.9f );
Paint.ClearBrush();
}
rect = rect.Shrink( 4 );
// Icon
{
var iconRect = rect;
iconRect.Width = iconRect.Height;
iconRect = iconRect.Shrink( 8 );
Paint.SetBrushAndPen( Theme.Grey.WithAlpha( 0.4f ) );
if ( !string.IsNullOrWhiteSpace( c.Thumb ) && !c.Thumb.StartsWith( "/" ) )
{
Paint.Draw( iconRect, c.Thumb );
}
else
{
Paint.DrawRect( iconRect, 4 );
}
rect.Left = iconRect.Right + 8;
}
//header
{
rect.Top += 8;
Paint.SetFont( "Poppins", 11, 450 );
Paint.Pen = pen;
var r = Paint.DrawText( rect, c.Title, TextFlag.LeftTop );
r.Left = r.Right + 8;
r.Right = rect.Right;
r.Bottom -= 3;
rect.Top = r.Bottom + 4;
var installedVersion = library is not null ? $"v{library?.Version}" : "";
Paint.Pen = pen.WithAlphaMultiplied( 0.6f );
Paint.SetDefaultFont();
Paint.DrawText( r, $"{c.Org.Title} {installedVersion}", TextFlag.LeftBottom );
}
// body text
{
Paint.Pen = pen.WithAlphaMultiplied( 0.6f );
Paint.SetDefaultFont();
Paint.DrawText( rect, c.Summary, TextFlag.LeftTop );
}
}
protected override void OnPaint()
{
Paint.ClearPen();
Paint.SetBrush( Theme.ControlBackground );
Paint.DrawRect( LocalRect );
Paint.Antialiasing = true;
Paint.TextAntialiasing = true;
base.OnPaint();
}
[EditorEvent.Frame]
public void Frame()
{
if ( !Visible ) return;
if ( ShowInstalled )
{
if ( SetContentHash( GetContentHash(), 0.5f ) )
{
SetItems(LibrarySystem.All.Where(x => x.Project.Package.Ident != "libraryimporter"));
SelectItem(LibrarySystem.All.FirstOrDefault(x => x.Project.Package.Ident != "libraryimporter") );
}
}
if ( ShowAvailable )
{
if ( SetContentHash( GetContentHash(), 0.5f ) )
{
_ = UpdateAsync();
}
}
}
async Task UpdateAsync()
{
var search = "";
if ( !string.IsNullOrEmpty( Filter ) ) search = Filter + " ";
var found = await Package.FindAsync( $"{search}type:library sort:updated", 200, 0 );
SetItems( found.Packages );
SelectItem( found.Packages.FirstOrDefault() );
}
int GetContentHash()
{
if ( ShowInstalled )
return LibrarySystem.All.GetHashCode();
return HashCode.Combine( Filter );
}
}
Game
library
public sealed class BouncyBone : TransformProxyComponent
{
JiggleBoneState state = new JiggleBoneState();
[Property]
public Vector3 Influence { get; set; } = new Vector3( 1, 1, 1 );
[Property, Range( 0, 50.0f )]
public float Stiffness { get; set; } = 1;
[Property, Range( 0, 50.0f )]
public float Damping { get; set; } = 1;
Transform LocalJigglePosition;
TransformSpring springer;
protected override void OnEnabled()
{
springer = new TransformSpring();
springer.Transform = Transform.World;
LocalJigglePosition = springer.Transform;
base.OnEnabled();
}
protected override void OnUpdate()
{
var oldPos = LocalJigglePosition;
using ( Transform.DisableProxy() )
{
var worldTx = Transform.World;
springer.Stiffness = Stiffness;
springer.Damping = Damping;
springer.UpdateSpring( Transform.World, Time.Delta );
var tx = GameObject.Parent.Transform.World.ToLocal( springer.Transform );
LocalJigglePosition = tx;
}
if ( oldPos != LocalJigglePosition )
{
MarkTransformChanged();
}
}
public override Transform GetLocalTransform()
{
return LocalJigglePosition;
}
}
public struct TransformSpring
{
public Transform Transform;
private Vector3 velocityPosition;
private Vector3 velocityScale;
private Rotation velocityRotation = Rotation.Identity;
public float Stiffness = 1.5f; // Spring stiffness, higher is stiffer
public float Damping = 1.0f; // Damping, higher is less oscillation
public TransformSpring()
{
Transform = global::Transform.Zero;
}
public void UpdateSpring( Transform target, float deltaTime )
{
Transform.Position = SpringLerp( Transform.Position, target.Position, ref velocityPosition, deltaTime );
Transform.Scale = SpringLerp( Transform.Scale, target.Scale, ref velocityScale, deltaTime );
Transform.Rotation = target.Rotation;
}
private Vector3 SpringLerp( Vector3 current, Vector3 target, ref Vector3 velocity, float deltaTime )
{
float omega = 2f * MathF.PI * Stiffness;
float damper = MathF.Exp( -Damping * deltaTime * omega );
Vector3 displacement = current - target;
Vector3 springForce = -omega * omega * displacement;
Vector3 dampingForce = -2f * omega * Damping * velocity;
Vector3 acceleration = springForce + dampingForce;
velocity = (velocity + acceleration * deltaTime) * damper;
return target + displacement + velocity * deltaTime;
}
}
Game
library
using Sandbox;
/// <summary>
/// This is a component - in your library!
/// </summary>
[Title( "LibraryImporter - My Component" )]
public class MyLibraryComponent : Component
{
}
Game
library
global using static Sandbox.Internal.GlobalGameNamespace;
[assembly: global::System.Reflection.AssemblyMetadata( "AddonTitle", "LibraryImporter" )]
[assembly: global::System.Reflection.AssemblyMetadata( "AddonIdent", "libraryimporter" )]
[assembly: global::System.Reflection.AssemblyMetadata( "OrgIdent", "coomzy" )]
[assembly: global::System.Reflection.AssemblyMetadata( "Ident", "coomzy.libraryimporter" )]
[assembly: global::System.Reflection.AssemblyMetadata( "CompileTime", "9/2/2024 10:47:22 PM" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineVersion", "17" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineMinorVersion", "1" )]
[assembly: System.Runtime.Versioning.TargetFramework( ".NETCoreApp,Version=v7.0", FrameworkDisplayName = ".NET 7.0" )]
[assembly: global::System.Reflection.AssemblyVersion("0.0.126.0")]
[assembly: global::System.Reflection.AssemblyFileVersion("0.0.126.0")]
Editor
library
global using Editor;
global using System.Collections.Generic;
global using System.Linq;
global using Sandbox;
using System;
using System.IO;
using Sandbox.Internal;
public static class LibraryImporter
{
//[Event("localaddons.changed")]
public static void CheckCookiePrompt()
{
if (ProjectCookie == null)
{
return;
}
var hasOfferedLibraryImport = ProjectCookie.Get("easysaving.hasOfferedLibraryImport", false);
if (!hasOfferedLibraryImport)
{
ProjectCookie.Set("easysaving.hasOfferedLibraryImport", true);
System.Reflection.MethodInfo saveMethod = ProjectCookie.GetType().GetMethod("Save", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (saveMethod != null)
{
saveMethod.Invoke(ProjectCookie, null);
}
else
{
Log.Error("CookieContainer Method 'Save' not found");
}
OpenMyMenu();
}
}
public static void OpenMyMenu()
{
var confirm = new PopupWindow(
"Import UI from Library? (Easy Saving)",
"UI doesn't work in libraries, so if you want the options screens that comes with this library you need to import it into your project",
"No",
new Dictionary<string, Action>()
{
{ "Yes", () => ImportDisabledLibraryFiles() }
}
);
confirm.Show();
}
[Menu("Editor", "Library/Easy Saving/Import Disabled Library Files")]
public static void ImportDisabledLibraryFiles()
{
string libraryFolder = new System.IO.DirectoryInfo(Editor.FileSystem.Content.GetFullPath("/")).FullName.Replace("Assets", "");
string projectFolder = new System.IO.DirectoryInfo(Editor.FileSystem.ProjectSettings.GetFullPath("/")).FullName.Replace("ProjectSettings", "");
string source = $"{libraryFolder}code";
string target = @$"{projectFolder}code\libraries";
string[] files = Directory.GetFiles(source, "*.DISABLED", SearchOption.AllDirectories);
Log.Info($"Moving .DISABLED files from source: {source}");
foreach (string file in files)
{
string relativePath = Path.GetRelativePath(source, file);
string targetPath = Path.Combine(target, relativePath);
targetPath = RemoveDisabledExtension(targetPath);
Directory.CreateDirectory(Path.GetDirectoryName(targetPath));
if (File.Exists(targetPath))
{
if ((File.GetAttributes(targetPath) & FileAttributes.ReadOnly) != 0)
{
File.SetAttributes(targetPath, FileAttributes.Normal);
}
File.Delete(targetPath);
}
File.Copy(file, targetPath);
Log.Info($"Moved: {file} -> {targetPath}");
}
Log.Info("All files have been moved successfully.");
}
static string RemoveDisabledExtension(string filePath)
{
if (filePath.EndsWith(".DISABLED", StringComparison.OrdinalIgnoreCase))
{
return filePath.Substring(0, filePath.Length - ".DISABLED".Length);
}
return filePath;
}
}
Game
library
global using Sandbox; global using System;
Game
library
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
namespace Sandbox.Events;
/// <summary>
/// Interface for event payloads that can be listened for by <see cref="IGameEventHandler{T}"/>s.
/// </summary>
public interface IGameEvent { }
/// <summary>
/// Interface for components that handle game events with a payload of type <see cref="T"/>.
/// </summary>
/// <typeparam name="T">Event payload type.</typeparam>
public interface IGameEventHandler<in T>
where T : IGameEvent
{
/// <summary>
/// Called when an event with payload of type <see cref="T"/> is dispatched on a <see cref="GameObject"/>
/// that contains this component, including on a descendant.
/// </summary>
/// <param name="eventArgs">Event payload.</param>
void OnGameEvent( T eventArgs );
}
/// <summary>
/// Helper for dispatching game events in a scene.
/// </summary>
public static class GameEvent
{
private static Dictionary<Type, IReadOnlyDictionary<Type, int>> HandlerOrderingCache { get; } = new();
/// <summary>
/// Notifies all <see cref="IGameEventHandler{T}"/> components that are within <paramref name="root"/>,
/// with a payload of type <typeparamref name="T"/>.
/// </summary>
public static void Dispatch<T>( this GameObject root, T eventArgs )
where T : IGameEvent
{
var handlers = (root is Scene scene
? scene.GetAllComponents<IGameEventHandler<T>>() // I think this is more efficient?
: root.Components.GetAll<IGameEventHandler<T>>())
.ToArray();
if ( !HandlerOrderingCache.TryGetValue( typeof(T), out var ordering ) || handlers.Any( x => !ordering.ContainsKey( x.GetType() ) ) )
{
ordering = HandlerOrderingCache[typeof(T)] = GetHandlerOrdering<T>();
}
List<Exception>? exceptions = null;
foreach ( var handler in handlers.OrderBy( x => ordering[x.GetType()] ) )
{
try
{
handler.OnGameEvent( eventArgs );
}
catch ( Exception e )
{
exceptions ??= new();
exceptions.Add( e );
}
}
switch ( exceptions?.Count )
{
case 1:
Log.Error( exceptions[0] );
break;
case > 1:
Log.Error( new AggregateException( exceptions ) );
break;
}
}
private static bool IsImplementingMethodName( string methodName )
{
if ( methodName == nameof(IGameEventHandler<IGameEvent>.OnGameEvent) )
{
return true;
}
return methodName.StartsWith( "Sandbox.Events.IGameEventHandler<" ) && methodName.EndsWith( ">.OnGameEvent" );
}
private static MethodDescription? GetImplementation<T>( TypeDescription type )
{
foreach ( var method in type.Methods )
{
if ( method.IsStatic ) continue;
if ( method.Parameters.Length != 1 ) continue;
if ( method.Parameters[0].ParameterType != typeof( T ) ) continue;
if ( !IsImplementingMethodName( method.Name ) ) continue;
return method;
}
return null;
}
private static IReadOnlyDictionary<Type, int> GetHandlerOrdering<T>()
where T : IGameEvent
{
var types = TypeLibrary.GetTypes<IGameEventHandler<T>>().ToArray();
var helper = new SortingHelper( types.Length );
for ( var i = 0; i < types.Length; ++i )
{
var type = types[i];
var method = GetImplementation<T>( type );
if ( method is null )
{
Log.Warning( $"Can't find {nameof( IGameEventHandler<T> )}<{typeof( T ).Name}> implementation in {type.Name}!" );
continue;
}
foreach ( var attrib in method.Attributes )
{
switch ( attrib )
{
case EarlyAttribute:
helper.AddFirst( i );
break;
case LateAttribute:
helper.AddLast( i );
break;
case IBeforeAttribute before:
for ( var j = 0; j < types.Length; ++j )
{
if ( i == j ) continue;
var other = types[j];
if ( before.Type.IsAssignableFrom( other.TargetType ) )
{
helper.AddConstraint( i, j );
}
}
break;
case IAfterAttribute after:
for ( var j = 0; j < types.Length; ++j )
{
if ( i == j ) continue;
var other = types[j];
if ( after.Type.IsAssignableFrom( other.TargetType ) )
{
helper.AddConstraint( j, i );
}
}
break;
}
}
}
var ordering = new List<int>();
if ( !helper.Sort( ordering, out var invalid ) )
{
Log.Error( $"Invalid event ordering constraint between {types[invalid.EarlierIndex].Name} and {types[invalid.LaterIndex].Name}!" );
return ImmutableDictionary<Type, int>.Empty;
}
return Enumerable.Range( 0, ordering.Count )
.ToImmutableDictionary( i => types[ordering[i]].TargetType, i => i );
}
}
public delegate void GameEventAction<in T>( T eventArgs )
where T : IGameEvent;
/// <summary>
/// Base class for components that expose game events to Action Graph.
/// </summary>
public abstract class GameEventComponent<T> : Component, IGameEventHandler<T>
where T : IGameEvent
{
/// <summary>
/// Action invoked when the <typeparamref name="T"/> event is dispatched.
/// </summary>
[Property]
public GameEventAction<T>? OnEvent { get; set; }
void IGameEventHandler<T>.OnGameEvent( T eventArgs )
{
OnEvent?.Invoke( eventArgs );
}
}
Game
library
global using Microsoft.AspNetCore.Components; global using Microsoft.AspNetCore.Components.Rendering;
Game
library
using System;
namespace Sandbox.Events;
/// <summary>
/// Only valid on <see cref="IGameEventHandler{T}.OnGameEvent"/> implementations. Forces this
/// event handler to be invoked before any handlers not marked as early, except if more specific
/// constraints are given (i.e., <see cref="BeforeAttribute{T}"/>, <see cref="AfterAttribute{T}"/>).
/// </summary>
[AttributeUsage( AttributeTargets.Method )]
public sealed class EarlyAttribute : Attribute
{
}
/// <summary>
/// Only valid on <see cref="IGameEventHandler{T}.OnGameEvent"/> implementations. Forces this
/// event handler to be invoked after any handlers not marked as late, except if more specific
/// constraints are given (i.e., <see cref="BeforeAttribute{T}"/>, <see cref="AfterAttribute{T}"/>).
/// </summary>
[AttributeUsage( AttributeTargets.Method )]
public sealed class LateAttribute : Attribute
{
}
internal interface IBeforeAttribute
{
Type Type { get; }
}
internal interface IAfterAttribute
{
Type Type { get; }
}
/// <summary>
/// Only valid on <see cref="IGameEventHandler{T}.OnGameEvent"/> implementations. Forces this
/// event handler to be invoked before any handlers in the specified type.
/// </summary>
[AttributeUsage( AttributeTargets.Method, AllowMultiple = true )]
public sealed class BeforeAttribute<T> : Attribute, IBeforeAttribute
{
Type IBeforeAttribute.Type => typeof(T);
}
/// <summary>
/// Only valid on <see cref="IGameEventHandler{T}.OnGameEvent"/> implementations. Forces this
/// event handler to be invoked after any handlers in the specified type.
/// </summary>
[AttributeUsage( AttributeTargets.Method, AllowMultiple = true )]
public sealed class AfterAttribute<T> : Attribute, IAfterAttribute
{
Type IAfterAttribute.Type => typeof( T );
}
Editor
library
using Editor;
using System;
using System.IO;
namespace Editor.LibraryManager;
[Dock( "Editor", "Library Importer", "extension" )]
public class LibraryImporterDock : Widget
{
internal Action<string> OnValueChanged;
public LibraryImporterDock( Widget parent ) : base( parent )
{
Layout = Layout.Row();
Layout.Margin = 4;
Layout.Spacing = 4;
FocusMode = FocusMode.TabOrClickOrWheel;
}
[EditorEvent.Hotload]
void Rebuild()
{
Layout.Clear( true );
Layout.Add( new InstalledLibrariesWidgetProxy( this ) );
}
static void CopyFolder( string sourceDir, string targetDir, string libraryName, string libraryFolder )
{
if ( sourceDir.Contains( "\\.", StringComparison.OrdinalIgnoreCase ) )
{
return;
}
System.IO.Directory.CreateDirectory( targetDir );
foreach ( var file in Directory.GetFiles( sourceDir ) )
{
CopyAndProcessFile( file, targetDir, libraryName, libraryFolder );
}
foreach ( var directory in Directory.GetDirectories( sourceDir ) )
{
CopyFolder( directory, Path.Combine( targetDir, Path.GetFileName( directory ) ), libraryName, libraryFolder );
}
}
static void CopyAndProcessFile( string file, string targetDir, string libraryName, string libraryFolder )
{
var targetname = Path.Combine( targetDir, Path.GetFileName( file ) );
// Replace $ident with our ident in file name
targetname = targetname.Replace( "$title", libraryName );
targetname = targetname.Replace( "$ident", libraryFolder );
if ( file.EndsWith( ".cs" ) || file.EndsWith( ".json" ) || file.EndsWith( ".sbproj" ) )
{
var txt = System.IO.File.ReadAllText( file );
txt = txt.Replace( "$title", libraryName );
txt = txt.Replace( "$ident", libraryFolder );
System.IO.File.WriteAllText( targetname, txt );
}
else
{
File.Copy( file, targetname );
}
}
[EditorEvent.Frame]
public void Frame()
{
if ( SetContentHash( ContentHash, 0.5f ) )
{
Rebuild();
}
}
int ContentHash() => HashCode.Combine( 0 );
}
public class InstalledLibrariesWidgetProxy : Widget
{
Layout ContentLayout;
public InstalledLibrariesWidgetProxy( LibraryImporterDock manager )
{
Layout = Layout.Row();
var left = new LibraryListProxy( null )
{
ShowInstalled = true
};
Layout.Add( left, 1 );
ContentLayout = Layout.AddColumn();
left.OnLibrarySelected = ( library ) =>
{
ContentLayout.Clear( true );
ContentLayout.Add( new LibraryDetailProxy( library ) );
};
manager.OnValueChanged = ( txt ) =>
{
left.Filter = txt;
};
}
}
Editor
library
using System;
using System.Text.Json;
using System.Threading.Tasks;
using System.IO;
using static Editor.Label;
using System.Diagnostics;
using Editor;
public class LibraryDetailProxy : Widget
{
Package Package;
LibraryProject SelectedLibrary;
Button InstallButton;
Button UninstallButton;
ComboBox LibraryList;
LibraryProject ComboLibrarySelection;
Label ComboLibraryLabel;
Button InstallLibraryButton;
Button UninstallLibraryButton;
ImportedLibraries importedLibraries = new ImportedLibraries();
public string libraryStagingFolder => $@"{Project.Current.RootDirectory}\staging\libraryimporter";
public string projectAssetsFolder => GetAssetsFolder(Project.Current, Package);
public string projectCodeFolder => GetCodeFolder(Project.Current, Package);
public string projectEditorFolder => GetEditorFolder(Project.Current, Package);
public string selectedLibraryAssetsFolder => GetAssetsFolder(ComboLibrarySelection?.Project, SelectedLibrary?.Project?.Package);
public string selectedLibraryCodeFolder => GetCodeFolder(ComboLibrarySelection?.Project, SelectedLibrary?.Project?.Package);
public string selectedLibraryEditorFolder => GetEditorFolder(ComboLibrarySelection?.Project, SelectedLibrary?.Project?.Package);
public List<string> blacklistedFolders = new List<string> { "bin", "obj", "Properties" };
public List<string> blacklistedFileTypes = new List<string> { ".csproj" };
public string importingLibraryToLibraryID => $"{SelectedLibrary.Project.Package.Ident}-{ComboLibrarySelection.Project.Package.Ident}";
public LibraryDetailProxy( Package package ) : base()
{
Package = package;
Layout = Layout.Column();
Layout.Margin = 8;
Layout.Spacing = 4;
importedLibraries = ImportedLibraries.Load();
_ = FetchAndBuild();
}
async Task FetchAndBuild()
{
if ( !this.IsValid() ) return;
Layout.Clear( true );
if ( !Sandbox.Package.TryParseIdent( Package.FullIdent, out var ident ) )
return;
SelectedLibrary = LibrarySystem.All.FirstOrDefault(x => x.Project.Package.Ident == Package.Ident && x.Project.Package.Org == Package.Org);
bool hasStagedLibrary = importedLibraries.packageIndentToStagedVersion.ContainsKey(Package.Ident);
string stagedVersion = "";
if (hasStagedLibrary)
{
stagedVersion = importedLibraries.packageIndentToStagedVersion[Package.Ident];
}
bool stagedVersionDifferentFromInstalled = stagedVersion != SelectedLibrary.Version.ToString();
bool hasImportedLibraryToProject = importedLibraries.packageIndentToProjectImportedVersion.ContainsKey(Package.Ident);
string importedVersionToProject = "";
if (hasImportedLibraryToProject)
{
importedVersionToProject = importedLibraries.packageIndentToProjectImportedVersion[Package.Ident];
}
bool importedVersionDifferentFromInstalled = importedVersionToProject != SelectedLibrary.Version.ToString();
// do we have this package installed? what is the version?
if ( ident.org != "local" )
{
Package = await Package.FetchAsync( $"{ident.org}.{ident.package}", false ) ?? Package;
if ( !this.IsValid() ) return;
}
// Header, todo, icon and title
Layout.Add( new Label( $"{Package.Title} from {Package.Org.Title}" ) );
Layout.Add( new Label( $"Installed Version: {SelectedLibrary.Version}" ) );
if (!hasStagedLibrary)
{
Layout.Add(new Label($"Library has not been staged."));
InstallButton = new Button("Stage Library (move contents into staging folder)", this) { Pressed = () => MoveLibraryFolderToStaging(SelectedLibrary) };
Layout.Add(InstallButton);
Layout.AddStretchCell();
return;
}
Layout.Add(new Label(""));
Layout.Add(new Label("Staging"));
Layout.Add(new Label($"Staged Version: {stagedVersion}"));
if (stagedVersionDifferentFromInstalled)
{
InstallButton = new Button("Restage Library (move contents into staging folder)", this) { Pressed = () => MoveLibraryFolderToStaging(SelectedLibrary) };
Layout.Add(InstallButton);
}
Layout.Add(new Label(""));
Layout.Add(new Label($"Project"));
if (hasImportedLibraryToProject)
{
Layout.Add(new Label($"Imported Version: {importedVersionToProject}"));
}
if (hasImportedLibraryToProject && importedVersionDifferentFromInstalled)
{
InstallButton = new Button("Reimport to Project", this) { Pressed = () => CopyStagedLibraryToProject(SelectedLibrary) };
Layout.Add(InstallButton);
}
else if (!hasImportedLibraryToProject)
{
InstallButton = new Button("Import to Project", this) { Pressed = () => CopyStagedLibraryToProject(SelectedLibrary) };
Layout.Add(InstallButton);
}
if (hasImportedLibraryToProject)
{
UninstallButton = new Button("Remove from Project", this) { Pressed = () => DeleteImportedLibraryFolderFromProject(SelectedLibrary) };
Layout.Add(UninstallButton);
}
var otherLibraries = LibrarySystem.All.Where(x => x.Project.Package.Ident != "libraryimporter" && x.Project.Package.Ident != SelectedLibrary.Project.Package.Ident);
ComboLibrarySelection = otherLibraries.FirstOrDefault();
if (ComboLibrarySelection == null)
{
Layout.AddStretchCell();
return;
}
Layout.Add(new Label(""));
Layout.Add(new Label($"Library"));
bool hasImportedLibraryToLibrary = importedLibraries.packageIndentToLibraryImportedVersion.ContainsKey(importingLibraryToLibraryID);
string importedVersionToLibrary = "";
if (hasImportedLibraryToLibrary)
{
importedVersionToLibrary = importedLibraries.packageIndentToLibraryImportedVersion[importingLibraryToLibraryID];
}
bool importedVersionDifferentFromInstalledLibrary = importedVersionToLibrary != SelectedLibrary.Version.ToString();
ComboLibraryLabel = new Label($"");
Layout.Add(ComboLibraryLabel);
if (hasImportedLibraryToLibrary)
{
ComboLibraryLabel.Text = $"Imported Version: {importedVersionToLibrary}";
}
else
{
ComboLibraryLabel.Hide();
}
LibraryList = new ComboBox(this);
Layout.Add(LibraryList);
foreach (var otherLibrary in otherLibraries)
{
LibraryList.AddItem($"{otherLibrary.Project.Package.Title}", null, () => OnComboBoxLibrarySelected(otherLibrary), null, ComboLibrarySelection == otherLibrary);
}
InstallLibraryButton = new Button("Reimport to Library", this) { Pressed = () => CopyStagedLibraryToLibrary(SelectedLibrary, ComboLibrarySelection) };
Layout.Add(InstallLibraryButton);
if (!hasImportedLibraryToLibrary)
{
InstallLibraryButton.Text = "Import to Library";
}
if (hasImportedLibraryToLibrary && !importedVersionDifferentFromInstalledLibrary)
{
InstallLibraryButton.Hide();
}
UninstallLibraryButton = new Button("Remove from Library", this) { Pressed = () => DeleteImportedLibraryFolderFromLibrary() };
Layout.Add(UninstallLibraryButton);
if (!hasImportedLibraryToLibrary)
{
UninstallLibraryButton.Hide();
}
// information about this
Layout.AddStretchCell();
}
void OnComboBoxLibrarySelected(LibraryProject libraryProject)
{
ComboLibrarySelection = libraryProject;
// TODO: Check the state of export
// Duplicated logic, but fuck it I'm bored of this
bool hasImportedLibraryToLibrary = importedLibraries.packageIndentToLibraryImportedVersion.ContainsKey(importingLibraryToLibraryID);
string importedVersionToLibrary = "";
if (hasImportedLibraryToLibrary)
{
importedVersionToLibrary = importedLibraries.packageIndentToLibraryImportedVersion[importingLibraryToLibraryID];
}
bool importedVersionDifferentFromInstalledLibrary = importedVersionToLibrary != SelectedLibrary.Version.ToString();
if (hasImportedLibraryToLibrary)
{
if (importedVersionDifferentFromInstalledLibrary)
{
InstallLibraryButton.Text = "Reimport to Library";
InstallLibraryButton.Show();
}
else
{
InstallLibraryButton.Hide();
}
UninstallLibraryButton.Show();
}
else
{
InstallLibraryButton.Text = "Import to Library";
InstallLibraryButton.Show();
UninstallLibraryButton.Hide();
}
}
void CopyStagedLibraryToProject(LibraryProject libraryProject)
{
if (libraryProject?.Project?.Package == null)
{
Log.Error($"CopyStagedLibraryToProject() error null libraryProject!!!");
return;
}
string libraryRootFolder = @$"{libraryStagingFolder}\{libraryProject.Project.Package.Ident}";
string libraryCodeFolder = @$"{libraryRootFolder}\code";
string libraryEditorFolder = @$"{libraryRootFolder}\Editor";
string libraryAssetsFolder = @$"{libraryRootFolder}\Assets";
CopyFiles(libraryCodeFolder, projectCodeFolder);
CopyFiles(libraryEditorFolder, projectEditorFolder);
CopyFiles(libraryCodeFolder, projectAssetsFolder);
importedLibraries.packageIndentToProjectImportedVersion[Package.Ident] = libraryProject.Version.ToString();
importedLibraries.Save();
_ = FetchAndBuild();
}
void CopyStagedLibraryToLibrary(LibraryProject libraryProject, LibraryProject targetLibraryProject)
{
if (libraryProject?.Project?.Package == null || targetLibraryProject?.Project?.Package == null)
{
Log.Error($"CopyStagedLibraryToLibrary() error null libraryProject or targetLibraryProject!!!");
return;
}
string libraryRootFolder = @$"{libraryStagingFolder}\{libraryProject.Project.Package.Ident}";
string libraryCodeFolder = @$"{libraryRootFolder}\code";
string libraryEditorFolder = @$"{libraryRootFolder}\Editor";
string libraryAssetsFolder = @$"{libraryRootFolder}\Assets";
CopyFiles(libraryCodeFolder, selectedLibraryCodeFolder);
CopyFiles(libraryEditorFolder, selectedLibraryCodeFolder);
CopyFiles(libraryCodeFolder, selectedLibraryAssetsFolder);
importedLibraries.packageIndentToLibraryImportedVersion[importingLibraryToLibraryID] = libraryProject.Version.ToString();
importedLibraries.Save();
_ = FetchAndBuild();
}
void MoveLibraryFolderToStaging(LibraryProject libraryProject)
{
if (libraryProject?.Project?.Package == null)
{
Log.Error($"MoveLibraryFolderToStaging() error null libraryProject!!!");
return;
}
string stageFolderRoot = @$"{libraryStagingFolder}\{libraryProject.Project.Package.Ident}\";
string libraryRootFolder = $"{libraryProject.Project.RootDirectory.FullName}";
string libraryCodeFolder = @$"{libraryRootFolder}\code";
string libraryEditorFolder = @$"{libraryRootFolder}\Editor";
string libraryAssetsFolder = @$"{libraryRootFolder}\Assets";
string libraryUnitTestFolder = @$"{libraryRootFolder}\UnitTests";
string targetCodeFolder = @$"{libraryStagingFolder}\{libraryProject.Project.Package.Ident}\code";
string targetEditorFolder = @$"{libraryStagingFolder}\{libraryProject.Project.Package.Ident}\Editor";
string targetAssetsFolder = @$"{libraryStagingFolder}\{libraryProject.Project.Package.Ident}\Assets";
if (Directory.Exists(stageFolderRoot))
{
Directory.Delete(stageFolderRoot, true);
}
CopyFiles(libraryCodeFolder, targetCodeFolder, true);
CopyFiles(libraryEditorFolder, targetEditorFolder, true);
CopyFiles(libraryAssetsFolder, targetAssetsFolder, true);
try
{
// I don't care about unit tests, so fuck em
if (Directory.Exists(libraryUnitTestFolder))
{
Directory.Delete(libraryUnitTestFolder, true);
}
}
catch
{
Log.Error("Could not delete libraryUnitTestFolder, you'll have to do it manually.");
}
importedLibraries.packageIndentToStagedVersion[libraryProject.Project.Package.Ident] = libraryProject.Version.ToString();
importedLibraries.Save();
_ = FetchAndBuild();
}
void CopyFiles(string sourceFolder, string targetFolder, bool moveNotCopy = false)
{
var relativePaths = GetRelativeFilePaths(sourceFolder, blacklistedFolders, blacklistedFileTypes);
//Log.Info($"CopyFiles() relativePaths: {relativePaths.Count()}");
foreach (var path in relativePaths)
{
var sourcePath = @$"{sourceFolder}\{path}";
var targetPath = @$"{targetFolder}\{path}";
var targetDirectory = Path.GetDirectoryName(targetPath);
if (!Directory.Exists(targetDirectory))
{
Directory.CreateDirectory(targetDirectory);
}
if (moveNotCopy)
{
File.Move(sourcePath, targetPath, true);
}
else
{
File.Copy(sourcePath, targetPath, true);
}
//Log.Info($"{(moveNotCopy ? "Move" : "Copy")} sourcePath: {sourcePath} to targetPath: {targetPath}");
}
}
void DeleteImportedLibraryFolderFromProject(LibraryProject libraryProject)
{
if (Directory.Exists(projectCodeFolder))
{
Directory.Delete(projectCodeFolder, true);
}
if (Directory.Exists(projectEditorFolder))
{
Directory.Delete(projectEditorFolder, true);
}
if (Directory.Exists(projectAssetsFolder))
{
Directory.Delete(projectAssetsFolder, true);
}
string packageIdent = libraryProject.Project.Package.Ident;
if (importedLibraries.packageIndentToProjectImportedVersion.ContainsKey(packageIdent))
{
importedLibraries.packageIndentToProjectImportedVersion.Remove(packageIdent);
importedLibraries.Save();
}
_ = FetchAndBuild();
}
void DeleteImportedLibraryFolderFromLibrary()
{
if (Directory.Exists(selectedLibraryCodeFolder))
{
Directory.Delete(selectedLibraryCodeFolder, true);
}
if (Directory.Exists(selectedLibraryEditorFolder))
{
Directory.Delete(selectedLibraryEditorFolder, true);
}
if (Directory.Exists(selectedLibraryAssetsFolder))
{
Directory.Delete(selectedLibraryAssetsFolder, true);
}
if (importedLibraries.packageIndentToLibraryImportedVersion.ContainsKey(importingLibraryToLibraryID))
{
importedLibraries.packageIndentToLibraryImportedVersion.Remove(importingLibraryToLibraryID);
importedLibraries.Save();
}
_ = FetchAndBuild();
}
void DeleteImportedLibraryFolderInLibrary(string packageIndent)
{
string folder = @$"{selectedLibraryCodeFolder}";
string folderEditor = @$"{selectedLibraryEditorFolder}";
if (Directory.Exists(folder))
{
Directory.Delete(folder, true);
}
if (Directory.Exists(folderEditor))
{
Directory.Delete(folderEditor, true);
}
string id = $"{ComboLibrarySelection.Project.Package.Ident}-{Package.Ident}";
if (importedLibraries.packageIndentToLibraryImportedVersion.ContainsKey(id))
{
importedLibraries.packageIndentToLibraryImportedVersion.Remove(id);
importedLibraries.Save();
}
}
static IEnumerable<string> GetRelativeFilePaths(string rootDirectory, List<string> blacklistedFolders, List<string> blacklistedFileTypes)
{
var filePaths = new List<string>();
if (!Directory.Exists(rootDirectory))
{
return filePaths;
}
foreach (var filePath in Directory.EnumerateFiles(rootDirectory, "*.*", SearchOption.AllDirectories))
{
// Check if the file is in a blacklisted folder
var directoryName = Path.GetDirectoryName(filePath);
if (blacklistedFolders.Any(folder => directoryName.Contains(folder, StringComparison.OrdinalIgnoreCase)))
{
continue;
}
// Check if the file has a blacklisted extension
var fileExtension = Path.GetExtension(filePath);
if (blacklistedFileTypes.Contains(fileExtension, StringComparer.OrdinalIgnoreCase))
{
continue;
}
// Get the relative path
var relativePath = Path.GetRelativePath(rootDirectory, filePath);
filePaths.Add(relativePath);
}
return filePaths;
}
public string GetAssetsFolder(Project inProject, Package inPackage)
{
if (inProject == null || inPackage == null)
return null;
string folder = @$"{inProject.GetAssetsPath()}\libraries\{inPackage.Ident}";
return folder;
}
public string GetCodeFolder(Project inProject, Package inPackage)
{
if (inProject == null || inPackage == null)
return null;
string folder = @$"{inProject.GetCodePath()}\libraries\{inPackage.Ident}";
return folder;
}
public string GetEditorFolder(Project inProject, Package inPackage)
{
if (inProject == null || inPackage == null)
return null;
string folder = @$"{inProject.GetRootPath()}\Editor\libraries\{inPackage.Ident}";
return folder;
}
protected override Vector2 SizeHint()
{
return new Vector2( 300, 100 );
}
}
public class ImportedLibraries
{
public Dictionary<string, string> packageIndentToProjectImportedVersion { get; set; } = new Dictionary<string, string>();
public Dictionary<string, string> packageIndentToLibraryImportedVersion { get; set; } = new Dictionary<string, string>();
public Dictionary<string, string> packageIndentToStagedVersion { get; set; } = new Dictionary<string, string>();
static string libraryStagingFolder => $@"{Project.Current.RootDirectory}\staging\libraryimporter";
static string saveFilePath => @$"{libraryStagingFolder}\imported_libraries.json";
public void Save()
{
var json = JsonSerializer.Serialize(this, GetSerializerOptions());
System.IO.File.WriteAllText(saveFilePath, json);
}
public static ImportedLibraries Load()
{
if (!System.IO.File.Exists(saveFilePath))
{
return new ImportedLibraries();
}
var fileContents = System.IO.File.ReadAllText(saveFilePath);
var inst = JsonSerializer.Deserialize<ImportedLibraries>(fileContents, GetSerializerOptions());
return inst;
}
public static JsonSerializerOptions GetSerializerOptions()
{
var serializerOptions = new JsonSerializerOptions();
serializerOptions.WriteIndented = true;
return serializerOptions;
}
}
Game
library
using System.Collections.Generic;
using System.Linq;
namespace Sandbox.Events;
/// <summary>
/// Generate an ordering based on a set of first-most and last-most items, and
/// individual constraints between pairs of items. All first-most items will be
/// ordered before all last-most items, and any other items will be put in the
/// middle unless forced to be elsewhere by a constraint.
/// </summary>
internal class SortingHelper
{
public record struct SortConstraint( int EarlierIndex, int LaterIndex )
{
public SortConstraint Complement => new ( LaterIndex, EarlierIndex );
}
private readonly int _itemCount;
private readonly HashSet<SortConstraint> _initialConstraints = new HashSet<SortConstraint>();
private readonly HashSet<int> _first = new HashSet<int>();
private readonly HashSet<int> _last = new HashSet<int>();
public SortingHelper( int itemCount )
{
_itemCount = itemCount;
}
public void AddConstraint( int earlierIndex, int laterIndex )
{
_initialConstraints.Add( new SortConstraint( earlierIndex, laterIndex ) );
}
public void AddFirst( int earlierIndex )
{
_first.Add( earlierIndex );
}
public void AddLast( int laterIndex )
{
_last.Add( laterIndex );
}
public bool Sort( List<int> result, out SortConstraint invalidConstraint )
{
var middle = new HashSet<int>();
for ( var index = 0; index < _itemCount; ++index )
{
if ( !_first.Contains( index ) && !_last.Contains( index ) )
middle.Add( index );
}
var allConstraints = new HashSet<SortConstraint>();
var newConstraints = new Queue<SortConstraint>();
var beforeDict = new Dictionary<int, HashSet<int>>();
var afterDict = new Dictionary<int, HashSet<int>>();
bool AddWorkingConstraint( int earlierIndex, int laterIndex, out SortConstraint constraint )
{
constraint = new SortConstraint( earlierIndex, laterIndex );
if ( allConstraints.Contains( constraint.Complement ) )
return false;
if ( !allConstraints.Add( constraint ) )
return true;
newConstraints.Enqueue( constraint );
if ( !beforeDict.TryGetValue( earlierIndex, out var before ) )
beforeDict.Add( earlierIndex, before = new HashSet<int>() );
if ( !afterDict.TryGetValue( laterIndex, out var after ) )
afterDict.Add( laterIndex, after = new HashSet<int>() );
before.Add( laterIndex );
after.Add( earlierIndex );
return true;
}
// Add initial constraints
foreach ( var initialConstraint in _initialConstraints )
{
if ( !AddWorkingConstraint( initialConstraint.EarlierIndex, initialConstraint.LaterIndex, out invalidConstraint ) )
return false;
}
// Everything in _first should be before everything in _last
foreach ( var earlierIndex in _first )
{
foreach ( var laterIndex in _last )
{
if ( !AddWorkingConstraint( earlierIndex, laterIndex, out invalidConstraint ) )
return false;
}
}
// Keep propagating constraints until nothing changes
while ( newConstraints.TryDequeue( out var nextConstraint ) )
{
// if a < b, and b < c, then a < c etc
if ( beforeDict.TryGetValue( nextConstraint.LaterIndex, out var before ) )
{
foreach ( var laterIndex in before )
{
if ( !AddWorkingConstraint( nextConstraint.EarlierIndex, laterIndex, out invalidConstraint ) )
return false;
}
}
if ( afterDict.TryGetValue( nextConstraint.EarlierIndex, out var after ) )
{
foreach ( var earlierIndex in after )
{
if ( !AddWorkingConstraint( earlierIndex, nextConstraint.LaterIndex, out invalidConstraint ) )
{
return false;
}
}
}
}
// Now if we have any items that aren't using GroupOrder.First, and haven't
// determined that they are ordered before another item with GroupOrder.First,
// we can safely order them after all GroupOrder.First items. And vice versa.
foreach ( var middleIndex in middle )
{
var isBeforeAnyFirst = beforeDict.TryGetValue( middleIndex, out var before )
&& before.Any( x => _first.Contains( x ) );
var isAfterAnyLast = afterDict.TryGetValue( middleIndex, out var after )
&& after.Any( x => _last.Contains( x ) );
if ( !isBeforeAnyFirst )
{
foreach ( var earlierIndex in _first )
AddWorkingConstraint( earlierIndex, middleIndex, out invalidConstraint );
}
if ( !isAfterAnyLast )
{
foreach ( var laterIndex in _last )
AddWorkingConstraint( middleIndex, laterIndex, out invalidConstraint );
}
}
// Now lets add items to the final ordering if all items that should be sorted
// before them are already added to that ordering. We'll implement this by choosing
// items that have an empty list / don't appear in afterDict, and update that
// dictionary as we go.
var earliestRemaining = new Queue<int>();
// First, seed the queue with everything that's already not ordered after anything
for ( var index = 0; index < _itemCount; ++index )
{
if ( !afterDict.ContainsKey( index ) )
{
earliestRemaining.Enqueue( index );
}
}
result.Clear();
while ( earliestRemaining.TryDequeue( out var nextIndex ) )
{
result.Add( nextIndex );
foreach ( var laterIndex in beforeDict.TryGetValue( nextIndex, out var laterIndices )
? laterIndices : Enumerable.Empty<int>() )
{
var beforeLater = afterDict[laterIndex];
beforeLater.Remove( nextIndex );
if ( beforeLater.Count == 0 )
earliestRemaining.Enqueue( laterIndex );
}
}
invalidConstraint = default;
return result.Count == _itemCount;
}
}
Debug: View Raw JSON Response
{
"TotalCount": 16,
"Files": [
{
"Ident": "coomzy.libraryimporter",
"Path": "JiggleBone.cs",
"FileName": "JiggleBone.cs",
"PackageType": "library",
"CodeKind": "Game",
"AssetVersionId": 71858,
"IsPrivate": false,
"Code": "public sealed class JiggleBone : TransformProxyComponent\r\n{\r\n\tJiggleBoneState state = new JiggleBoneState();\r\n\r\n\t[Property]\r\n\tpublic Vector3 StartPoint = new Vector3( 0, 0, 0 );\r\n\r\n\t[Property]\r\n\tpublic Vector3 EndPoint = new Vector3( 32, 0, 0 );\r\n\r\n\t[Property, Range( 0, 2 )]\r\n\tpublic float Speed { get; set; } = 1.0f;\r\n\r\n\t[Property, Range( 0, 2 )]\r\n\tpublic float Stiffness { get; set; } = 1.0f;\r\n\r\n\t[Property, Range( 0, 2 )]\r\n\tpublic float Damping { get; set; } = 1.0f;\r\n\r\n\t[Property, Range( 0, 100 )]\r\n\tpublic float Radius { get; set; } = 40.0f;\r\n\r\n\t[Property, Range( 0, 100 )]\r\n\tpublic float Mass { get; set; } = 1.0f;\r\n\r\n\tTransform LocalJigglePosition;\r\n\r\n\tprotected override void OnEnabled()\r\n\t{\r\n\t\tLocalJigglePosition = Transform.Local;\r\n\r\n\t\tbase.OnEnabled();\r\n\r\n\t\tstate = new JiggleBoneState();\r\n\t}\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tvar oldPos = LocalJigglePosition;\r\n\r\n\r\n\r\n\t\tusing ( Transform.DisableProxy() )\r\n\t\t{\r\n\t\t\tvar worldTx = Transform.World;\r\n\r\n\t\t\tvar startPoint = worldTx.PointToWorld( StartPoint );\r\n\t\t\tvar endPoint = worldTx.PointToWorld( EndPoint );\r\n\r\n\t\t\t//Gizmo.Draw.LineSphere( startPoint, 1 );\r\n\t\t\t//Gizmo.Draw.LineSphere( endPoint, 1 );\r\n\r\n\t\t\tstate.Extent = (endPoint - startPoint);\r\n\t\t\tstate.Stiffness = Stiffness;\r\n\t\t\tstate.Damping = Damping;\r\n\t\t\tstate.Radius = Radius;\r\n\t\t\tstate.Mass = Mass;\r\n\r\n\t\t\tstate.Update( startPoint, Time.Delta * Speed * 16.0f );\r\n\r\n\t\t\tvar tx = worldTx.RotateAround( startPoint, state.Rotation );\r\n\t\t\tLocalJigglePosition = GameObject.Parent.Transform.World.ToLocal( tx );\r\n\t\t}\r\n\r\n\t\tif ( oldPos != LocalJigglePosition )\r\n\t\t{\r\n\t\t\tMarkTransformChanged();\r\n\t\t}\r\n\t}\r\n\r\n\tprotected override void DrawGizmos()\r\n\t{\r\n\t\tbase.DrawGizmos();\r\n\r\n\t\tif ( !Gizmo.IsSelected )\r\n\t\t\treturn;\r\n\r\n\t\tusing ( Transform.DisableProxy() )\r\n\t\t{\r\n\t\t\tGizmo.Transform = Transform.World;\r\n\t\t\tGizmo.Draw.IgnoreDepth = false;\r\n\t\t\tGizmo.Draw.Color = Gizmo.Colors.Yaw.WithAlpha( 0.5f );\r\n\t\t\tGizmo.Draw.Line( StartPoint, EndPoint );\r\n\t\t\tGizmo.Draw.LineBBox( BBox.FromPositionAndSize( StartPoint, 5 ) );\r\n\t\t\tGizmo.Draw.LineBBox( BBox.FromPositionAndSize( EndPoint, 5 ) );\r\n\t\t\tGizmo.Draw.LineSphere( EndPoint, Radius * 2.0f, 4 );\r\n\t\t}\r\n\t}\r\n\r\n\tpublic override Transform GetLocalTransform()\r\n\t{\r\n\t\treturn LocalJigglePosition;\r\n\t}\r\n}\r\n\r\nclass JiggleBoneState\r\n{\r\n\tpublic Vector3 Extent = new Vector3( 32, 0, 0 );\r\n\r\n\tpublic Vector3 Position { get; set; }\r\n\tpublic Rotation Rotation { get; set; }\r\n\tpublic float Stiffness { get; set; } = 1.0f;\r\n\tpublic float Damping { get; set; } = 1.0f;\r\n\tpublic float Radius { get; set; } = 10.0f;\r\n\tpublic float Gravity { get; set; } = 1.0f;\r\n\tpublic float Mass { get; set; } = 1.0f;\r\n\r\n\r\n\tVector3 basePosition;\r\n\tVector3 velocity;\r\n\r\n\tpublic JiggleBoneState()\r\n\t{\r\n\r\n\t}\r\n\r\n\tinternal void Update( Vector3 position, float timeDelta )\r\n\t{\r\n\t\tbasePosition = position + Extent;\r\n\r\n\t\t// initialization\r\n\t\tif ( Position == default )\r\n\t\t{\r\n\t\t\tPosition = basePosition;\r\n\t\t}\r\n\r\n\t\t// Calculate spring force based on displacement from the cube\r\n\t\tVector3 displacement = Position - basePosition;\r\n\t\tVector3 springForce = -Stiffness * displacement;\r\n\r\n\t\t// Calculate acceleration (Newton's second law)\r\n\t\tVector3 acceleration = springForce / Mass;\r\n\r\n\t\t// Update velocity (integrate acceleration)\r\n\t\tvelocity += acceleration * timeDelta;\r\n\r\n\t\t// Apply exponential damping\r\n\t\tvelocity *= (float)Math.Exp( -Damping * timeDelta );\r\n\r\n\t\t// Update position (integrate velocity)\r\n\t\tPosition += velocity * timeDelta;\r\n\r\n\t\t{\r\n\t\t\tvar diff = Position - basePosition;\r\n\t\t\tvar diffLen = diff.Length;\r\n\t\t\tif ( diffLen > Radius )\r\n\t\t\t{\r\n\t\t\t\tPosition = basePosition + diff.Normal * Radius;\r\n\t\t\t\t//velocity = velocity.AddClamped( -diff * 2.0f, diff.Length );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Store the rotation offset result\r\n\t\tRotation = Rotation.FromToRotation( basePosition - position, Position - position );\r\n\r\n\t\t//Gizmo.Draw.IgnoreDepth = true;\r\n\t\t//Gizmo.Draw.Line( position, Position );\r\n\t\t//Gizmo.Draw.Line( basePosition, Position );\r\n\t}\r\n}\r\n"
},
{
"Ident": "coomzy.libraryimporter",
"Path": "MyLibraryComponent.cs",
"FileName": "MyLibraryComponent.cs",
"PackageType": "library",
"CodeKind": "Game",
"AssetVersionId": 71858,
"IsPrivate": false,
"Code": "using Sandbox;\r\n\r\n/// <summary>\r\n/// This is a component - in your library!\r\n/// </summary>\r\n[Title( \"LibraryImporter - My Component\" )]\r\npublic class MyLibraryComponent : Component\r\n{\r\n\r\n}\r\n"
},
{
"Ident": "coomzy.libraryimporter",
"Path": "UnitTests/LibraryTest.cs",
"FileName": "LibraryTest.cs",
"PackageType": "library",
"CodeKind": "UnitTest",
"AssetVersionId": 71858,
"IsPrivate": false,
"Code": "using Sandbox;\r\n\r\n[TestClass]\r\npublic partial class LibraryTests\r\n{\r\n\t[TestMethod]\r\n\tpublic void SceneTest()\r\n\t{\r\n\t\tvar scene = new Scene();\r\n\t\tusing ( scene.Push() )\r\n\t\t{\r\n\t\t\tvar go = new GameObject();\r\n\r\n\t\t\tAssert.AreEqual( 1, scene.Directory.GameObjectCount );\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"
},
{
"Ident": "coomzy.libraryimporter",
"Path": "UnitTests/UnitTest.cs",
"FileName": "UnitTest.cs",
"PackageType": "library",
"CodeKind": "UnitTest",
"AssetVersionId": 71858,
"IsPrivate": false,
"Code": "global using Microsoft.VisualStudio.TestTools.UnitTesting;\r\n\r\n[TestClass]\r\npublic class TestInit\r\n{\r\n\t[AssemblyInitialize]\r\n\tpublic static void ClassInitialize( TestContext context )\r\n\t{\r\n\t\tSandbox.Application.InitUnitTest();\r\n\t}\r\n}\r\n"
},
{
"Ident": "coomzy.libraryimporter",
"Path": "Editor/LibraryListProxy.cs",
"FileName": "LibraryListProxy.cs",
"PackageType": "library",
"CodeKind": "Editor",
"AssetVersionId": 71858,
"IsPrivate": false,
"Code": "using System;\r\nusing System.Threading.Tasks;\r\n\r\nclass LibraryListProxy : ListView\r\n{\r\n\tpublic Action<Package> OnLibrarySelected { get; set; }\r\n\r\n\tpublic bool ShowInstalled { get; set; }\r\n\tpublic bool ShowAvailable { get; set; }\r\n\r\n\tpublic string Filter { get; set; } = \"\";\r\n\r\n\tpublic LibraryListProxy( Widget parent ) : base( parent )\r\n\t{\r\n\t\tItemContextMenu = ShowItemContext;\r\n\t\tItemSelected = OnItemClicked;\r\n\r\n\t\tItemSize = new Vector2( -1, 64 );\r\n\t}\r\n\r\n\tpublic void OnItemClicked( object value )\r\n\t{\r\n\t\tif ( value is LibraryProject lib )\r\n\t\t{\r\n\t\t\tOnLibrarySelected?.Invoke( lib.Project.Package );\r\n\t\t}\r\n\r\n\t\tif ( value is Package p )\r\n\t\t{\r\n\t\t\tOnLibrarySelected?.Invoke( p );\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void ShowItemContext( object obj )\r\n\t{\r\n\t\tLibraryProject project = obj as LibraryProject;\r\n\t\tPackage package = obj as Package ?? project?.Project.Package;\r\n\r\n\t\tvar m = new Menu();\r\n\r\n\t\tif ( package is not null && package.Org.Ident != \"local\" )\r\n\t\t{\r\n\t\t\tm.AddOption( \"View in Browser\", \"public\", () => EditorUtility.OpenFolder( package.Url ) );\r\n\t\t\tm.AddSeparator();\r\n\t\t}\r\n\r\n\t\tif ( project is not null )\r\n\t\t{\r\n\t\t\tm.AddOption( \"Project Properties\", \"tune\", () => _ = ProjectSettingsWindow.OpenForProject( project.Project ) );\r\n\t\t\tm.AddSeparator();\r\n\t\t\tm.AddOption( \"Publish Project\", \"upload_file\", () => _ = ProjectSettingsWindow.OpenForProject( project.Project, \"upload\" ) );\r\n\t\t\tm.AddSeparator();\r\n\t\t\tm.AddOption( \"Show in Explorer\", \"folder\", () => EditorUtility.OpenFolder( project.Project.RootDirectory.FullName ) );\r\n\t\t}\r\n\r\n\t\tm.OpenAt( Editor.Application.CursorPosition );\r\n\t}\r\n\r\n\tprotected override void PaintItem( VirtualWidget item )\r\n\t{\r\n\t\tif ( item.Object is LibraryProject c )\r\n\t\t\tPaintItem( item, c.Project.Package );\r\n\r\n\t\tif ( item.Object is Package p )\r\n\t\t\tPaintItem( item, p );\r\n\t}\r\n\r\n\tprivate void PaintItem( VirtualWidget item, Package c )\r\n\t{\r\n\t\tvar rect = item.Rect;\r\n\r\n\t\tif ( Paint.HasPressed )\r\n\t\t\trect = rect.Shrink( 2, 2, 0, 0 );\r\n\r\n\t\tvar library = LibrarySystem.All.Where( x => x.Project.Package.Ident == c.Ident && x.Project.Package.Org.Ident == c.Org.Ident && x.Project.Package.Ident != \"libraryimporter\").FirstOrDefault();\r\n\r\n\t\tvar pen = Color.White.WithAlpha( 0.6f );\r\n\r\n\t\tif ( Paint.HasMouseOver || Paint.HasSelected )\r\n\t\t{\r\n\t\t\tPaint.SetBrush( Theme.Primary.WithAlpha( Paint.HasMouseOver ? 0.8f : 0.5f ) );\r\n\t\t\tPaint.ClearPen();\r\n\t\t\tPaint.DrawRect( rect, 4 );\r\n\r\n\t\t\tpen = Color.White.WithAlpha( Paint.HasMouseOver ? 1 : 0.9f );\r\n\t\t\tPaint.ClearBrush();\r\n\t\t}\r\n\r\n\t\trect = rect.Shrink( 4 );\r\n\r\n\t\t// Icon\r\n\t\t{\r\n\t\t\tvar iconRect = rect;\r\n\t\t\ticonRect.Width = iconRect.Height;\r\n\t\t\ticonRect = iconRect.Shrink( 8 );\r\n\r\n\t\t\tPaint.SetBrushAndPen( Theme.Grey.WithAlpha( 0.4f ) );\r\n\r\n\t\t\tif ( !string.IsNullOrWhiteSpace( c.Thumb ) && !c.Thumb.StartsWith( \"/\" ) )\r\n\t\t\t{\r\n\t\t\t\tPaint.Draw( iconRect, c.Thumb );\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tPaint.DrawRect( iconRect, 4 );\r\n\t\t\t}\r\n\r\n\t\t\trect.Left = iconRect.Right + 8;\r\n\t\t}\r\n\r\n\t\t//header\r\n\t\t{\r\n\t\t\trect.Top += 8;\r\n\r\n\t\t\tPaint.SetFont( \"Poppins\", 11, 450 );\r\n\t\t\tPaint.Pen = pen;\r\n\t\t\tvar r = Paint.DrawText( rect, c.Title, TextFlag.LeftTop );\r\n\r\n\t\t\tr.Left = r.Right + 8;\r\n\t\t\tr.Right = rect.Right;\r\n\t\t\tr.Bottom -= 3;\r\n\r\n\t\t\trect.Top = r.Bottom + 4;\r\n\r\n\t\t\tvar installedVersion = library is not null ? $\"v{library?.Version}\" : \"\";\r\n\r\n\t\t\tPaint.Pen = pen.WithAlphaMultiplied( 0.6f );\r\n\t\t\tPaint.SetDefaultFont();\r\n\t\t\tPaint.DrawText( r, $\"{c.Org.Title} {installedVersion}\", TextFlag.LeftBottom );\r\n\t\t}\r\n\r\n\t\t// body text\r\n\t\t{\r\n\t\t\tPaint.Pen = pen.WithAlphaMultiplied( 0.6f );\r\n\t\t\tPaint.SetDefaultFont();\r\n\t\t\tPaint.DrawText( rect, c.Summary, TextFlag.LeftTop );\r\n\t\t}\r\n\t}\r\n\r\n\tprotected override void OnPaint()\r\n\t{\r\n\t\tPaint.ClearPen();\r\n\t\tPaint.SetBrush( Theme.ControlBackground );\r\n\t\tPaint.DrawRect( LocalRect );\r\n\r\n\t\tPaint.Antialiasing = true;\r\n\t\tPaint.TextAntialiasing = true;\r\n\r\n\t\tbase.OnPaint();\r\n\t}\r\n\r\n\t[EditorEvent.Frame]\r\n\tpublic void Frame()\r\n\t{\r\n\t\tif ( !Visible ) return;\r\n\r\n\t\tif ( ShowInstalled )\r\n\t\t{\r\n\t\t\tif ( SetContentHash( GetContentHash(), 0.5f ) )\r\n\t\t\t{\r\n\t\t\t\tSetItems(LibrarySystem.All.Where(x => x.Project.Package.Ident != \"libraryimporter\"));\r\n\t\t\t\tSelectItem(LibrarySystem.All.FirstOrDefault(x => x.Project.Package.Ident != \"libraryimporter\") );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( ShowAvailable )\r\n\t\t{\r\n\t\t\tif ( SetContentHash( GetContentHash(), 0.5f ) )\r\n\t\t\t{\r\n\t\t\t\t_ = UpdateAsync();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tasync Task UpdateAsync()\r\n\t{\r\n\t\tvar search = \"\";\r\n\t\tif ( !string.IsNullOrEmpty( Filter ) ) search = Filter + \" \";\r\n\t\tvar found = await Package.FindAsync( $\"{search}type:library sort:updated\", 200, 0 );\r\n\t\tSetItems( found.Packages );\r\n\r\n\t\tSelectItem( found.Packages.FirstOrDefault() );\r\n\t}\r\n\r\n\tint GetContentHash()\r\n\t{\r\n\t\tif ( ShowInstalled )\r\n\t\t\treturn LibrarySystem.All.GetHashCode();\r\n\r\n\t\treturn HashCode.Combine( Filter );\r\n\t}\r\n\r\n}\r\n"
},
{
"Ident": "coomzy.libraryimporter",
"Path": "BouncyBone.cs",
"FileName": "BouncyBone.cs",
"PackageType": "library",
"CodeKind": "Game",
"AssetVersionId": 71858,
"IsPrivate": false,
"Code": "public sealed class BouncyBone : TransformProxyComponent\r\n{\r\n\tJiggleBoneState state = new JiggleBoneState();\r\n\r\n\t[Property]\r\n\tpublic Vector3 Influence { get; set; } = new Vector3( 1, 1, 1 );\r\n\r\n\t[Property, Range( 0, 50.0f )]\r\n\tpublic float Stiffness { get; set; } = 1;\r\n\r\n\t[Property, Range( 0, 50.0f )]\r\n\tpublic float Damping { get; set; } = 1;\r\n\r\n\tTransform LocalJigglePosition;\r\n\tTransformSpring springer;\r\n\r\n\tprotected override void OnEnabled()\r\n\t{\r\n\t\tspringer = new TransformSpring();\r\n\t\tspringer.Transform = Transform.World;\r\n\t\tLocalJigglePosition = springer.Transform;\r\n\r\n\t\tbase.OnEnabled();\r\n\r\n\r\n\t}\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tvar oldPos = LocalJigglePosition;\r\n\r\n\t\tusing ( Transform.DisableProxy() )\r\n\t\t{\r\n\t\t\tvar worldTx = Transform.World;\r\n\r\n\t\t\tspringer.Stiffness = Stiffness;\r\n\t\t\tspringer.Damping = Damping;\r\n\t\t\tspringer.UpdateSpring( Transform.World, Time.Delta );\r\n\r\n\t\t\tvar tx = GameObject.Parent.Transform.World.ToLocal( springer.Transform );\r\n\t\t\tLocalJigglePosition = tx;\r\n\t\t}\r\n\r\n\t\tif ( oldPos != LocalJigglePosition )\r\n\t\t{\r\n\t\t\tMarkTransformChanged();\r\n\t\t}\r\n\t}\r\n\r\n\tpublic override Transform GetLocalTransform()\r\n\t{\r\n\t\treturn LocalJigglePosition;\r\n\t}\r\n}\r\n\r\n\r\npublic struct TransformSpring\r\n{\r\n\tpublic Transform Transform;\r\n\r\n\tprivate Vector3 velocityPosition;\r\n\tprivate Vector3 velocityScale;\r\n\tprivate Rotation velocityRotation = Rotation.Identity;\r\n\r\n\tpublic float Stiffness = 1.5f; // Spring stiffness, higher is stiffer\r\n\tpublic float Damping = 1.0f; // Damping, higher is less oscillation\r\n\r\n\tpublic TransformSpring()\r\n\t{\r\n\t\tTransform = global::Transform.Zero;\r\n\t}\r\n\r\n\tpublic void UpdateSpring( Transform target, float deltaTime )\r\n\t{\r\n\t\tTransform.Position = SpringLerp( Transform.Position, target.Position, ref velocityPosition, deltaTime );\r\n\t\tTransform.Scale = SpringLerp( Transform.Scale, target.Scale, ref velocityScale, deltaTime );\r\n\t\tTransform.Rotation = target.Rotation;\r\n\t}\r\n\r\n\tprivate Vector3 SpringLerp( Vector3 current, Vector3 target, ref Vector3 velocity, float deltaTime )\r\n\t{\r\n\t\tfloat omega = 2f * MathF.PI * Stiffness;\r\n\t\tfloat damper = MathF.Exp( -Damping * deltaTime * omega );\r\n\r\n\t\tVector3 displacement = current - target;\r\n\t\tVector3 springForce = -omega * omega * displacement;\r\n\t\tVector3 dampingForce = -2f * omega * Damping * velocity;\r\n\r\n\t\tVector3 acceleration = springForce + dampingForce;\r\n\t\tvelocity = (velocity + acceleration * deltaTime) * damper;\r\n\t\treturn target + displacement + velocity * deltaTime;\r\n\t}\r\n\r\n\r\n}\r\n"
},
{
"Ident": "coomzy.libraryimporter",
"Path": "Code/MyLibraryComponent.cs",
"FileName": "MyLibraryComponent.cs",
"PackageType": "library",
"CodeKind": "Game",
"AssetVersionId": 71858,
"IsPrivate": false,
"Code": "using Sandbox;\r\n\r\n/// <summary>\r\n/// This is a component - in your library!\r\n/// </summary>\r\n[Title( \"LibraryImporter - My Component\" )]\r\npublic class MyLibraryComponent : Component\r\n{\r\n\r\n}\r\n"
},
{
"Ident": "coomzy.libraryimporter",
"Path": ".obj/__compiler_extra.cs",
"FileName": "__compiler_extra.cs",
"PackageType": "library",
"CodeKind": "Game",
"AssetVersionId": 71858,
"IsPrivate": false,
"Code": "global using static Sandbox.Internal.GlobalGameNamespace;\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"AddonTitle\", \"LibraryImporter\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"AddonIdent\", \"libraryimporter\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"OrgIdent\", \"coomzy\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"Ident\", \"coomzy.libraryimporter\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"CompileTime\", \"9/2/2024 10:47:22 PM\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"EngineVersion\", \"17\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"EngineMinorVersion\", \"1\" )]\r\n\r\n[assembly: System.Runtime.Versioning.TargetFramework( \".NETCoreApp,Version=v7.0\", FrameworkDisplayName = \".NET 7.0\" )]\r\n[assembly: global::System.Reflection.AssemblyVersion(\"0.0.126.0\")]\r\n[assembly: global::System.Reflection.AssemblyFileVersion(\"0.0.126.0\")]"
},
{
"Ident": "coomzy.libraryimporter",
"Path": "Editor/LibraryImporter.cs",
"FileName": "LibraryImporter.cs",
"PackageType": "library",
"CodeKind": "Editor",
"AssetVersionId": 71858,
"IsPrivate": false,
"Code": "global using Editor;\r\nglobal using System.Collections.Generic;\r\nglobal using System.Linq;\r\nglobal using Sandbox;\r\nusing System;\r\nusing System.IO;\r\nusing Sandbox.Internal;\r\n\r\npublic static class LibraryImporter\r\n{\r\n\t//[Event(\"localaddons.changed\")]\r\n\tpublic static void CheckCookiePrompt()\r\n\t{\r\n\t\tif (ProjectCookie == null)\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar hasOfferedLibraryImport = ProjectCookie.Get(\"easysaving.hasOfferedLibraryImport\", false);\r\n\t\tif (!hasOfferedLibraryImport)\r\n\t\t{\r\n\t\t\tProjectCookie.Set(\"easysaving.hasOfferedLibraryImport\", true);\r\n\r\n\t\t\tSystem.Reflection.MethodInfo saveMethod = ProjectCookie.GetType().GetMethod(\"Save\", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);\r\n\r\n\t\t\tif (saveMethod != null)\r\n\t\t\t{\r\n\t\t\t\tsaveMethod.Invoke(ProjectCookie, null);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tLog.Error(\"CookieContainer Method 'Save' not found\");\r\n\t\t\t}\r\n\r\n\t\t\tOpenMyMenu();\r\n\t\t}\r\n\t}\r\n\r\n\tpublic static void OpenMyMenu()\r\n\t{\r\n\t\tvar confirm = new PopupWindow(\r\n\t\t\t\"Import UI from Library? (Easy Saving)\",\r\n\t\t\t\"UI doesn't work in libraries, so if you want the options screens that comes with this library you need to import it into your project\",\r\n\t\t\t\"No\",\r\n\t\t\tnew Dictionary<string, Action>()\r\n\t\t\t{\r\n\t\t\t\t{ \"Yes\", () => ImportDisabledLibraryFiles() }\r\n\t\t\t}\r\n\t\t);\r\n\r\n\t\tconfirm.Show();\r\n\t}\r\n\r\n\t[Menu(\"Editor\", \"Library/Easy Saving/Import Disabled Library Files\")]\r\n\tpublic static void ImportDisabledLibraryFiles()\r\n\t{\r\n\t\tstring libraryFolder = new System.IO.DirectoryInfo(Editor.FileSystem.Content.GetFullPath(\"/\")).FullName.Replace(\"Assets\", \"\");\r\n\t\tstring projectFolder = new System.IO.DirectoryInfo(Editor.FileSystem.ProjectSettings.GetFullPath(\"/\")).FullName.Replace(\"ProjectSettings\", \"\");\r\n\r\n\t\tstring source = $\"{libraryFolder}code\";\r\n\t\tstring target = @$\"{projectFolder}code\\libraries\";\r\n\r\n\t\tstring[] files = Directory.GetFiles(source, \"*.DISABLED\", SearchOption.AllDirectories);\r\n\r\n\t\tLog.Info($\"Moving .DISABLED files from source: {source}\");\r\n\r\n\t\tforeach (string file in files)\r\n\t\t{\r\n\t\t\tstring relativePath = Path.GetRelativePath(source, file);\r\n\t\t\tstring targetPath = Path.Combine(target, relativePath);\r\n\r\n\t\t\ttargetPath = RemoveDisabledExtension(targetPath);\r\n\r\n\t\t\tDirectory.CreateDirectory(Path.GetDirectoryName(targetPath));\r\n\r\n\t\t\tif (File.Exists(targetPath))\r\n\t\t\t{\r\n\t\t\t\tif ((File.GetAttributes(targetPath) & FileAttributes.ReadOnly) != 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tFile.SetAttributes(targetPath, FileAttributes.Normal);\r\n\t\t\t\t}\r\n\t\t\t\tFile.Delete(targetPath);\r\n\t\t\t}\r\n\t\t\tFile.Copy(file, targetPath);\r\n\r\n\t\t\tLog.Info($\"Moved: {file} -> {targetPath}\");\r\n\t\t}\r\n\r\n\t\tLog.Info(\"All files have been moved successfully.\");\r\n\t}\r\n\r\n\tstatic string RemoveDisabledExtension(string filePath)\r\n\t{\r\n\t\tif (filePath.EndsWith(\".DISABLED\", StringComparison.OrdinalIgnoreCase))\r\n\t\t{\r\n\t\t\treturn filePath.Substring(0, filePath.Length - \".DISABLED\".Length);\r\n\t\t}\r\n\t\treturn filePath;\r\n\t}\r\n}\r\n"
},
{
"Ident": "coomzy.libraryimporter",
"Path": "Assembly.cs",
"FileName": "Assembly.cs",
"PackageType": "library",
"CodeKind": "Game",
"AssetVersionId": 71858,
"IsPrivate": false,
"Code": "global using Sandbox;\r\nglobal using System;\r\n\r\n"
},
{
"Ident": "coomzy.libraryimporter",
"Path": "libraries/libevents/GameEvents/GameEvent.cs",
"FileName": "GameEvent.cs",
"PackageType": "library",
"CodeKind": "Game",
"AssetVersionId": 71858,
"IsPrivate": false,
"Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Collections.Immutable;\r\nusing System.Linq;\r\n\r\nnamespace Sandbox.Events;\r\n\r\n/// <summary>\r\n/// Interface for event payloads that can be listened for by <see cref=\"IGameEventHandler{T}\"/>s.\r\n/// </summary>\r\npublic interface IGameEvent { }\r\n\r\n/// <summary>\r\n/// Interface for components that handle game events with a payload of type <see cref=\"T\"/>.\r\n/// </summary>\r\n/// <typeparam name=\"T\">Event payload type.</typeparam>\r\npublic interface IGameEventHandler<in T>\r\n\twhere T : IGameEvent\r\n{\r\n\t/// <summary>\r\n\t/// Called when an event with payload of type <see cref=\"T\"/> is dispatched on a <see cref=\"GameObject\"/>\r\n\t/// that contains this component, including on a descendant.\r\n\t/// </summary>\r\n\t/// <param name=\"eventArgs\">Event payload.</param>\r\n\tvoid OnGameEvent( T eventArgs );\r\n}\r\n\r\n/// <summary>\r\n/// Helper for dispatching game events in a scene.\r\n/// </summary>\r\npublic static class GameEvent\r\n{\r\n\tprivate static Dictionary<Type, IReadOnlyDictionary<Type, int>> HandlerOrderingCache { get; } = new();\r\n\r\n\t/// <summary>\r\n\t/// Notifies all <see cref=\"IGameEventHandler{T}\"/> components that are within <paramref name=\"root\"/>,\r\n\t/// with a payload of type <typeparamref name=\"T\"/>.\r\n\t/// </summary>\r\n\tpublic static void Dispatch<T>( this GameObject root, T eventArgs )\r\n\t\twhere T : IGameEvent\r\n\t{\r\n\t\tvar handlers = (root is Scene scene\r\n\t\t\t? scene.GetAllComponents<IGameEventHandler<T>>() // I think this is more efficient?\r\n\t\t\t: root.Components.GetAll<IGameEventHandler<T>>())\r\n\t\t\t.ToArray();\r\n\r\n\t\tif ( !HandlerOrderingCache.TryGetValue( typeof(T), out var ordering ) || handlers.Any( x => !ordering.ContainsKey( x.GetType() ) ) )\r\n\t\t{\r\n\t\t\tordering = HandlerOrderingCache[typeof(T)] = GetHandlerOrdering<T>();\r\n\t\t}\r\n\r\n\t\tList<Exception>? exceptions = null;\r\n\r\n\t\tforeach ( var handler in handlers.OrderBy( x => ordering[x.GetType()] ) )\r\n\t\t{\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\thandler.OnGameEvent( eventArgs );\r\n\t\t\t}\r\n\t\t\tcatch ( Exception e )\r\n\t\t\t{\r\n\t\t\t\texceptions ??= new();\r\n\t\t\t\texceptions.Add( e );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tswitch ( exceptions?.Count )\r\n\t\t{\r\n\t\t\tcase 1:\r\n\t\t\t\tLog.Error( exceptions[0] );\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tcase > 1:\r\n\t\t\t\tLog.Error( new AggregateException( exceptions ) );\r\n\t\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\r\n\tprivate static bool IsImplementingMethodName( string methodName )\r\n\t{\r\n\t\tif ( methodName == nameof(IGameEventHandler<IGameEvent>.OnGameEvent) )\r\n\t\t{\r\n\t\t\treturn true;\r\n\t\t}\r\n\r\n\t\treturn methodName.StartsWith( \"Sandbox.Events.IGameEventHandler<\" ) && methodName.EndsWith( \">.OnGameEvent\" );\r\n\t}\r\n\r\n\tprivate static MethodDescription? GetImplementation<T>( TypeDescription type )\r\n\t{\r\n\t\tforeach ( var method in type.Methods )\r\n\t\t{\r\n\t\t\tif ( method.IsStatic ) continue;\r\n\t\t\tif ( method.Parameters.Length != 1 ) continue;\r\n\t\t\tif ( method.Parameters[0].ParameterType != typeof( T ) ) continue;\r\n\r\n\t\t\tif ( !IsImplementingMethodName( method.Name ) ) continue;\r\n\r\n\t\t\treturn method;\r\n\t\t}\r\n\r\n\t\treturn null;\r\n\t}\r\n\r\n\tprivate static IReadOnlyDictionary<Type, int> GetHandlerOrdering<T>()\r\n\t\twhere T : IGameEvent\r\n\t{\r\n\t\tvar types = TypeLibrary.GetTypes<IGameEventHandler<T>>().ToArray();\r\n\t\tvar helper = new SortingHelper( types.Length );\r\n\r\n\t\tfor ( var i = 0; i < types.Length; ++i )\r\n\t\t{\r\n\t\t\tvar type = types[i];\r\n\t\t\tvar method = GetImplementation<T>( type );\r\n\r\n\t\t\tif ( method is null )\r\n\t\t\t{\r\n\t\t\t\tLog.Warning( $\"Can't find {nameof( IGameEventHandler<T> )}<{typeof( T ).Name}> implementation in {type.Name}!\" );\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tforeach ( var attrib in method.Attributes )\r\n\t\t\t{\r\n\t\t\t\tswitch ( attrib )\r\n\t\t\t\t{\r\n\t\t\t\t\tcase EarlyAttribute:\r\n\t\t\t\t\t\thelper.AddFirst( i );\r\n\t\t\t\t\t\tbreak;\r\n\r\n\t\t\t\t\tcase LateAttribute:\r\n\t\t\t\t\t\thelper.AddLast( i );\r\n\t\t\t\t\t\tbreak;\r\n\r\n\t\t\t\t\tcase IBeforeAttribute before:\r\n\t\t\t\t\t\tfor ( var j = 0; j < types.Length; ++j )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tif ( i == j ) continue;\r\n\r\n\t\t\t\t\t\t\tvar other = types[j];\r\n\r\n\t\t\t\t\t\t\tif ( before.Type.IsAssignableFrom( other.TargetType ) )\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\thelper.AddConstraint( i, j );\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tbreak;\r\n\r\n\t\t\t\t\tcase IAfterAttribute after:\r\n\t\t\t\t\t\tfor ( var j = 0; j < types.Length; ++j )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tif ( i == j ) continue;\r\n\r\n\t\t\t\t\t\t\tvar other = types[j];\r\n\r\n\t\t\t\t\t\t\tif ( after.Type.IsAssignableFrom( other.TargetType ) )\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\thelper.AddConstraint( j, i );\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tvar ordering = new List<int>();\r\n\r\n\t\tif ( !helper.Sort( ordering, out var invalid ) )\r\n\t\t{\r\n\t\t\tLog.Error( $\"Invalid event ordering constraint between {types[invalid.EarlierIndex].Name} and {types[invalid.LaterIndex].Name}!\" );\r\n\t\t\treturn ImmutableDictionary<Type, int>.Empty;\r\n\t\t}\r\n\r\n\t\treturn Enumerable.Range( 0, ordering.Count )\r\n\t\t\t.ToImmutableDictionary( i => types[ordering[i]].TargetType, i => i );\r\n\t}\r\n}\r\n\r\npublic delegate void GameEventAction<in T>( T eventArgs )\r\n\twhere T : IGameEvent;\r\n\r\n/// <summary>\r\n/// Base class for components that expose game events to Action Graph.\r\n/// </summary>\r\npublic abstract class GameEventComponent<T> : Component, IGameEventHandler<T>\r\n\twhere T : IGameEvent\r\n{\r\n\t/// <summary>\r\n\t/// Action invoked when the <typeparamref name=\"T\"/> event is dispatched.\r\n\t/// </summary>\r\n\t[Property]\r\n\tpublic GameEventAction<T>? OnEvent { get; set; }\r\n\r\n\tvoid IGameEventHandler<T>.OnGameEvent( T eventArgs )\r\n\t{\r\n\t\tOnEvent?.Invoke( eventArgs );\r\n\t}\r\n}\r\n"
},
{
"Ident": "coomzy.libraryimporter",
"Path": "__gen_RazorNamespace.cs",
"FileName": "__gen_RazorNamespace.cs",
"PackageType": "library",
"CodeKind": "Game",
"AssetVersionId": 71858,
"IsPrivate": false,
"Code": "global using Microsoft.AspNetCore.Components; \nglobal using Microsoft.AspNetCore.Components.Rendering;\n"
},
{
"Ident": "coomzy.libraryimporter",
"Path": "libraries/libevents/GameEvents/Attributes.cs",
"FileName": "Attributes.cs",
"PackageType": "library",
"CodeKind": "Game",
"AssetVersionId": 71858,
"IsPrivate": false,
"Code": "\r\nusing System;\r\n\r\nnamespace Sandbox.Events;\r\n\r\n/// <summary>\r\n/// Only valid on <see cref=\"IGameEventHandler{T}.OnGameEvent\"/> implementations. Forces this\r\n/// event handler to be invoked before any handlers not marked as early, except if more specific\r\n/// constraints are given (i.e., <see cref=\"BeforeAttribute{T}\"/>, <see cref=\"AfterAttribute{T}\"/>).\r\n/// </summary>\r\n[AttributeUsage( AttributeTargets.Method )]\r\npublic sealed class EarlyAttribute : Attribute\r\n{\r\n\r\n}\r\n\r\n/// <summary>\r\n/// Only valid on <see cref=\"IGameEventHandler{T}.OnGameEvent\"/> implementations. Forces this\r\n/// event handler to be invoked after any handlers not marked as late, except if more specific\r\n/// constraints are given (i.e., <see cref=\"BeforeAttribute{T}\"/>, <see cref=\"AfterAttribute{T}\"/>).\r\n/// </summary>\r\n[AttributeUsage( AttributeTargets.Method )]\r\npublic sealed class LateAttribute : Attribute\r\n{\r\n\r\n}\r\n\r\ninternal interface IBeforeAttribute\r\n{\r\n\tType Type { get; }\r\n}\r\n\r\ninternal interface IAfterAttribute\r\n{\r\n\tType Type { get; }\r\n}\r\n\r\n/// <summary>\r\n/// Only valid on <see cref=\"IGameEventHandler{T}.OnGameEvent\"/> implementations. Forces this\r\n/// event handler to be invoked before any handlers in the specified type.\r\n/// </summary>\r\n[AttributeUsage( AttributeTargets.Method, AllowMultiple = true )]\r\npublic sealed class BeforeAttribute<T> : Attribute, IBeforeAttribute\r\n{\r\n\tType IBeforeAttribute.Type => typeof(T);\r\n}\r\n\r\n/// <summary>\r\n/// Only valid on <see cref=\"IGameEventHandler{T}.OnGameEvent\"/> implementations. Forces this\r\n/// event handler to be invoked after any handlers in the specified type.\r\n/// </summary>\r\n[AttributeUsage( AttributeTargets.Method, AllowMultiple = true )]\r\npublic sealed class AfterAttribute<T> : Attribute, IAfterAttribute\r\n{\r\n\tType IAfterAttribute.Type => typeof( T );\r\n}\r\n"
},
{
"Ident": "coomzy.libraryimporter",
"Path": "Editor/LibraryImporterDock.cs",
"FileName": "LibraryImporterDock.cs",
"PackageType": "library",
"CodeKind": "Editor",
"AssetVersionId": 71858,
"IsPrivate": false,
"Code": "using Editor;\r\nusing System;\r\nusing System.IO;\r\n\r\nnamespace Editor.LibraryManager;\r\n\r\n[Dock( \"Editor\", \"Library Importer\", \"extension\" )]\r\npublic class LibraryImporterDock : Widget\r\n{\r\n\tinternal Action<string> OnValueChanged;\r\n\r\n\tpublic LibraryImporterDock( Widget parent ) : base( parent )\r\n\t{\r\n\t\tLayout = Layout.Row();\r\n\t\tLayout.Margin = 4;\r\n\t\tLayout.Spacing = 4;\r\n\r\n\t\tFocusMode = FocusMode.TabOrClickOrWheel;\r\n\t}\r\n\r\n\t[EditorEvent.Hotload]\r\n\tvoid Rebuild()\r\n\t{\r\n\t\tLayout.Clear( true );\r\n\t\tLayout.Add( new InstalledLibrariesWidgetProxy( this ) );\r\n\t}\r\n\r\n\tstatic void CopyFolder( string sourceDir, string targetDir, string libraryName, string libraryFolder )\r\n\t{\r\n\t\tif ( sourceDir.Contains( \"\\\\.\", StringComparison.OrdinalIgnoreCase ) )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tSystem.IO.Directory.CreateDirectory( targetDir );\r\n\r\n\t\tforeach ( var file in Directory.GetFiles( sourceDir ) )\r\n\t\t{\r\n\t\t\tCopyAndProcessFile( file, targetDir, libraryName, libraryFolder );\r\n\t\t}\r\n\r\n\t\tforeach ( var directory in Directory.GetDirectories( sourceDir ) )\r\n\t\t{\r\n\t\t\tCopyFolder( directory, Path.Combine( targetDir, Path.GetFileName( directory ) ), libraryName, libraryFolder );\r\n\t\t}\r\n\t}\r\n\r\n\tstatic void CopyAndProcessFile( string file, string targetDir, string libraryName, string libraryFolder )\r\n\t{\r\n\t\tvar targetname = Path.Combine( targetDir, Path.GetFileName( file ) );\r\n\r\n\t\t// Replace $ident with our ident in file name\r\n\t\ttargetname = targetname.Replace( \"$title\", libraryName );\r\n\t\ttargetname = targetname.Replace( \"$ident\", libraryFolder );\r\n\r\n\t\tif ( file.EndsWith( \".cs\" ) || file.EndsWith( \".json\" ) || file.EndsWith( \".sbproj\" ) )\r\n\t\t{\r\n\t\t\tvar txt = System.IO.File.ReadAllText( file );\r\n\t\t\ttxt = txt.Replace( \"$title\", libraryName );\r\n\t\t\ttxt = txt.Replace( \"$ident\", libraryFolder );\r\n\t\t\tSystem.IO.File.WriteAllText( targetname, txt );\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tFile.Copy( file, targetname );\r\n\t\t}\r\n\t}\r\n\r\n\t[EditorEvent.Frame]\r\n\tpublic void Frame()\r\n\t{\r\n\t\tif ( SetContentHash( ContentHash, 0.5f ) )\r\n\t\t{\r\n\t\t\tRebuild();\r\n\t\t}\r\n\t}\r\n\r\n\tint ContentHash() => HashCode.Combine( 0 );\r\n\r\n}\r\n\r\npublic class InstalledLibrariesWidgetProxy : Widget\r\n{\r\n\tLayout ContentLayout;\r\n\r\n\tpublic InstalledLibrariesWidgetProxy( LibraryImporterDock manager )\r\n\t{\r\n\t\tLayout = Layout.Row();\r\n\r\n\t\tvar left = new LibraryListProxy( null )\r\n\t\t{\r\n\t\t\tShowInstalled = true\r\n\t\t};\r\n\r\n\t\tLayout.Add( left, 1 );\r\n\t\tContentLayout = Layout.AddColumn();\r\n\r\n\t\tleft.OnLibrarySelected = ( library ) =>\r\n\t\t{\r\n\t\t\tContentLayout.Clear( true );\r\n\t\t\tContentLayout.Add( new LibraryDetailProxy( library ) );\r\n\t\t};\r\n\r\n\t\tmanager.OnValueChanged = ( txt ) =>\r\n\t\t{\r\n\t\t\tleft.Filter = txt;\r\n\t\t};\r\n\t}\r\n}\r\n"
},
{
"Ident": "coomzy.libraryimporter",
"Path": "Editor/LibraryDetailProxy.cs",
"FileName": "LibraryDetailProxy.cs",
"PackageType": "library",
"CodeKind": "Editor",
"AssetVersionId": 71858,
"IsPrivate": false,
"Code": "using System;\r\nusing System.Text.Json;\r\nusing System.Threading.Tasks;\r\nusing System.IO;\r\nusing static Editor.Label;\r\nusing System.Diagnostics;\r\nusing Editor;\r\n\r\npublic class LibraryDetailProxy : Widget\r\n{\r\n\tPackage Package;\r\n\tLibraryProject SelectedLibrary;\r\n\r\n\tButton InstallButton;\r\n\tButton UninstallButton;\r\n\r\n\tComboBox LibraryList;\r\n\tLibraryProject ComboLibrarySelection;\r\n\tLabel ComboLibraryLabel;\r\n\r\n\tButton InstallLibraryButton;\r\n\tButton UninstallLibraryButton;\r\n\r\n\tImportedLibraries importedLibraries = new ImportedLibraries();\r\n\r\n\tpublic string libraryStagingFolder => $@\"{Project.Current.RootDirectory}\\staging\\libraryimporter\";\r\n\r\n\tpublic string projectAssetsFolder => GetAssetsFolder(Project.Current, Package);\r\n\tpublic string projectCodeFolder => GetCodeFolder(Project.Current, Package);\r\n\tpublic string projectEditorFolder => GetEditorFolder(Project.Current, Package);\r\n\r\n\tpublic string selectedLibraryAssetsFolder => GetAssetsFolder(ComboLibrarySelection?.Project, SelectedLibrary?.Project?.Package);\r\n\tpublic string selectedLibraryCodeFolder => GetCodeFolder(ComboLibrarySelection?.Project, SelectedLibrary?.Project?.Package);\r\n\tpublic string selectedLibraryEditorFolder => GetEditorFolder(ComboLibrarySelection?.Project, SelectedLibrary?.Project?.Package);\r\n\r\n\tpublic List<string> blacklistedFolders = new List<string> { \"bin\", \"obj\", \"Properties\" };\r\n\tpublic List<string> blacklistedFileTypes = new List<string> { \".csproj\" };\r\n\r\n\tpublic string importingLibraryToLibraryID => $\"{SelectedLibrary.Project.Package.Ident}-{ComboLibrarySelection.Project.Package.Ident}\";\r\n\r\n\tpublic LibraryDetailProxy( Package package ) : base()\r\n\t{\r\n\t\tPackage = package;\r\n\r\n\t\tLayout = Layout.Column();\r\n\t\tLayout.Margin = 8;\r\n\t\tLayout.Spacing = 4;\r\n\r\n\t\timportedLibraries = ImportedLibraries.Load();\r\n\r\n\t\t_ = FetchAndBuild();\r\n\t}\r\n\r\n\tasync Task FetchAndBuild()\r\n\t{\r\n\t\tif ( !this.IsValid() ) return;\r\n\r\n\t\tLayout.Clear( true );\r\n\r\n\t\tif ( !Sandbox.Package.TryParseIdent( Package.FullIdent, out var ident ) )\r\n\t\t\treturn;\r\n\r\n\t\tSelectedLibrary = LibrarySystem.All.FirstOrDefault(x => x.Project.Package.Ident == Package.Ident && x.Project.Package.Org == Package.Org);\r\n\r\n\t\tbool hasStagedLibrary = importedLibraries.packageIndentToStagedVersion.ContainsKey(Package.Ident);\r\n\t\tstring stagedVersion = \"\";\r\n\t\tif (hasStagedLibrary)\r\n\t\t{\r\n\t\t\tstagedVersion = importedLibraries.packageIndentToStagedVersion[Package.Ident];\r\n\t\t}\r\n\t\tbool stagedVersionDifferentFromInstalled = stagedVersion != SelectedLibrary.Version.ToString();\r\n\r\n\t\tbool hasImportedLibraryToProject = importedLibraries.packageIndentToProjectImportedVersion.ContainsKey(Package.Ident);\r\n\t\tstring importedVersionToProject = \"\";\r\n\t\tif (hasImportedLibraryToProject)\r\n\t\t{\r\n\t\t\timportedVersionToProject = importedLibraries.packageIndentToProjectImportedVersion[Package.Ident];\r\n\t\t}\r\n\t\tbool importedVersionDifferentFromInstalled = importedVersionToProject != SelectedLibrary.Version.ToString();\r\n\r\n\t\t// do we have this package installed? what is the version?\r\n\t\tif ( ident.org != \"local\" )\r\n\t\t{\r\n\t\t\tPackage = await Package.FetchAsync( $\"{ident.org}.{ident.package}\", false ) ?? Package;\r\n\t\t\tif ( !this.IsValid() ) return;\r\n\t\t}\r\n\r\n\t\t// Header, todo, icon and title\r\n\t\tLayout.Add( new Label( $\"{Package.Title} from {Package.Org.Title}\" ) );\r\n\t\tLayout.Add( new Label( $\"Installed Version: {SelectedLibrary.Version}\" ) );\r\n\r\n\t\tif (!hasStagedLibrary)\r\n\t\t{\r\n\t\t\tLayout.Add(new Label($\"Library has not been staged.\"));\r\n\r\n\t\t\tInstallButton = new Button(\"Stage Library (move contents into staging folder)\", this) { Pressed = () => MoveLibraryFolderToStaging(SelectedLibrary) };\r\n\t\t\tLayout.Add(InstallButton);\r\n\t\t\tLayout.AddStretchCell();\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tLayout.Add(new Label(\"\"));\r\n\t\tLayout.Add(new Label(\"Staging\"));\r\n\t\tLayout.Add(new Label($\"Staged Version: {stagedVersion}\"));\r\n\t\tif (stagedVersionDifferentFromInstalled)\r\n\t\t{\r\n\t\t\tInstallButton = new Button(\"Restage Library (move contents into staging folder)\", this) { Pressed = () => MoveLibraryFolderToStaging(SelectedLibrary) };\r\n\t\t\tLayout.Add(InstallButton);\r\n\t\t}\r\n\r\n\t\tLayout.Add(new Label(\"\"));\r\n\t\tLayout.Add(new Label($\"Project\"));\r\n\r\n\t\tif (hasImportedLibraryToProject)\r\n\t\t{\r\n\t\t\tLayout.Add(new Label($\"Imported Version: {importedVersionToProject}\"));\r\n\t\t}\r\n\r\n\t\tif (hasImportedLibraryToProject && importedVersionDifferentFromInstalled)\r\n\t\t{\r\n\t\t\tInstallButton = new Button(\"Reimport to Project\", this) { Pressed = () => CopyStagedLibraryToProject(SelectedLibrary) };\r\n\t\t\tLayout.Add(InstallButton);\r\n\t\t}\r\n\t\telse if (!hasImportedLibraryToProject)\r\n\t\t{\r\n\t\t\tInstallButton = new Button(\"Import to Project\", this) { Pressed = () => CopyStagedLibraryToProject(SelectedLibrary) };\r\n\t\t\tLayout.Add(InstallButton);\r\n\t\t}\r\n\r\n\t\tif (hasImportedLibraryToProject)\r\n\t\t{\r\n\t\t\tUninstallButton = new Button(\"Remove from Project\", this) { Pressed = () => DeleteImportedLibraryFolderFromProject(SelectedLibrary) };\r\n\t\t\tLayout.Add(UninstallButton);\r\n\t\t}\r\n\r\n\t\tvar otherLibraries = LibrarySystem.All.Where(x => x.Project.Package.Ident != \"libraryimporter\" && x.Project.Package.Ident != SelectedLibrary.Project.Package.Ident);\r\n\t\tComboLibrarySelection = otherLibraries.FirstOrDefault();\r\n\r\n\t\tif (ComboLibrarySelection == null)\r\n\t\t{\r\n\t\t\tLayout.AddStretchCell();\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tLayout.Add(new Label(\"\"));\r\n\t\tLayout.Add(new Label($\"Library\"));\r\n\r\n\t\tbool hasImportedLibraryToLibrary = importedLibraries.packageIndentToLibraryImportedVersion.ContainsKey(importingLibraryToLibraryID);\r\n\t\tstring importedVersionToLibrary = \"\";\r\n\t\tif (hasImportedLibraryToLibrary)\r\n\t\t{\r\n\t\t\timportedVersionToLibrary = importedLibraries.packageIndentToLibraryImportedVersion[importingLibraryToLibraryID];\r\n\t\t}\r\n\t\tbool importedVersionDifferentFromInstalledLibrary = importedVersionToLibrary != SelectedLibrary.Version.ToString();\r\n\r\n\t\tComboLibraryLabel = new Label($\"\");\r\n\t\tLayout.Add(ComboLibraryLabel);\r\n\r\n\t\tif (hasImportedLibraryToLibrary)\r\n\t\t{\r\n\t\t\tComboLibraryLabel.Text = $\"Imported Version: {importedVersionToLibrary}\";\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tComboLibraryLabel.Hide();\r\n\t\t}\r\n\r\n\t\tLibraryList = new ComboBox(this);\r\n\t\tLayout.Add(LibraryList);\r\n\r\n\t\tforeach (var otherLibrary in otherLibraries)\r\n\t\t{\r\n\t\t\tLibraryList.AddItem($\"{otherLibrary.Project.Package.Title}\", null, () => OnComboBoxLibrarySelected(otherLibrary), null, ComboLibrarySelection == otherLibrary);\r\n\t\t}\r\n\r\n\t\tInstallLibraryButton = new Button(\"Reimport to Library\", this) { Pressed = () => CopyStagedLibraryToLibrary(SelectedLibrary, ComboLibrarySelection) };\r\n\t\tLayout.Add(InstallLibraryButton);\r\n\r\n\t\tif (!hasImportedLibraryToLibrary)\r\n\t\t{\r\n\t\t\tInstallLibraryButton.Text = \"Import to Library\";\r\n\t\t}\r\n\r\n\t\tif (hasImportedLibraryToLibrary && !importedVersionDifferentFromInstalledLibrary)\r\n\t\t{\r\n\t\t\tInstallLibraryButton.Hide();\r\n\t\t}\r\n\r\n\t\tUninstallLibraryButton = new Button(\"Remove from Library\", this) { Pressed = () => DeleteImportedLibraryFolderFromLibrary() };\r\n\t\tLayout.Add(UninstallLibraryButton);\r\n\r\n\t\tif (!hasImportedLibraryToLibrary)\r\n\t\t{\r\n\t\t\tUninstallLibraryButton.Hide();\r\n\t\t}\r\n\r\n\t\t// information about this\r\n\t\tLayout.AddStretchCell();\r\n\t}\r\n\r\n\tvoid OnComboBoxLibrarySelected(LibraryProject libraryProject)\r\n\t{\r\n\t\tComboLibrarySelection = libraryProject;\r\n\t\t// TODO: Check the state of export\r\n\r\n\t\t// Duplicated logic, but fuck it I'm bored of this\r\n\t\tbool hasImportedLibraryToLibrary = importedLibraries.packageIndentToLibraryImportedVersion.ContainsKey(importingLibraryToLibraryID);\r\n\t\tstring importedVersionToLibrary = \"\";\r\n\t\tif (hasImportedLibraryToLibrary)\r\n\t\t{\r\n\t\t\timportedVersionToLibrary = importedLibraries.packageIndentToLibraryImportedVersion[importingLibraryToLibraryID];\r\n\t\t}\r\n\t\tbool importedVersionDifferentFromInstalledLibrary = importedVersionToLibrary != SelectedLibrary.Version.ToString();\r\n\r\n\r\n\t\tif (hasImportedLibraryToLibrary)\r\n\t\t{\r\n\t\t\tif (importedVersionDifferentFromInstalledLibrary)\r\n\t\t\t{\r\n\t\t\t\tInstallLibraryButton.Text = \"Reimport to Library\";\r\n\t\t\t\tInstallLibraryButton.Show();\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tInstallLibraryButton.Hide();\r\n\t\t\t}\r\n\r\n\t\t\tUninstallLibraryButton.Show();\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tInstallLibraryButton.Text = \"Import to Library\";\r\n\t\t\tInstallLibraryButton.Show();\r\n\t\t\tUninstallLibraryButton.Hide();\r\n\t\t}\r\n\t}\r\n\r\n\tvoid CopyStagedLibraryToProject(LibraryProject libraryProject)\r\n\t{\r\n\t\tif (libraryProject?.Project?.Package == null)\r\n\t\t{\r\n\t\t\tLog.Error($\"CopyStagedLibraryToProject() error null libraryProject!!!\");\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tstring libraryRootFolder = @$\"{libraryStagingFolder}\\{libraryProject.Project.Package.Ident}\";\r\n\r\n\t\tstring libraryCodeFolder = @$\"{libraryRootFolder}\\code\";\r\n\t\tstring libraryEditorFolder = @$\"{libraryRootFolder}\\Editor\";\r\n\t\tstring libraryAssetsFolder = @$\"{libraryRootFolder}\\Assets\";\r\n\r\n\t\tCopyFiles(libraryCodeFolder, projectCodeFolder);\r\n\t\tCopyFiles(libraryEditorFolder, projectEditorFolder);\r\n\t\tCopyFiles(libraryCodeFolder, projectAssetsFolder);\r\n\r\n\t\timportedLibraries.packageIndentToProjectImportedVersion[Package.Ident] = libraryProject.Version.ToString();\r\n\t\timportedLibraries.Save();\r\n\t\t_ = FetchAndBuild();\r\n\t}\r\n\r\n\tvoid CopyStagedLibraryToLibrary(LibraryProject libraryProject, LibraryProject targetLibraryProject)\r\n\t{\r\n\t\tif (libraryProject?.Project?.Package == null || targetLibraryProject?.Project?.Package == null)\r\n\t\t{\r\n\t\t\tLog.Error($\"CopyStagedLibraryToLibrary() error null libraryProject or targetLibraryProject!!!\");\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tstring libraryRootFolder = @$\"{libraryStagingFolder}\\{libraryProject.Project.Package.Ident}\";\r\n\r\n\t\tstring libraryCodeFolder = @$\"{libraryRootFolder}\\code\";\r\n\t\tstring libraryEditorFolder = @$\"{libraryRootFolder}\\Editor\";\r\n\t\tstring libraryAssetsFolder = @$\"{libraryRootFolder}\\Assets\";\r\n\r\n\t\tCopyFiles(libraryCodeFolder, selectedLibraryCodeFolder);\r\n\t\tCopyFiles(libraryEditorFolder, selectedLibraryCodeFolder);\r\n\t\tCopyFiles(libraryCodeFolder, selectedLibraryAssetsFolder);\r\n\r\n\t\timportedLibraries.packageIndentToLibraryImportedVersion[importingLibraryToLibraryID] = libraryProject.Version.ToString();\r\n\t\timportedLibraries.Save();\r\n\t\t_ = FetchAndBuild();\r\n\t}\r\n\r\n\tvoid MoveLibraryFolderToStaging(LibraryProject libraryProject)\r\n\t{\r\n\t\tif (libraryProject?.Project?.Package == null)\r\n\t\t{\r\n\t\t\tLog.Error($\"MoveLibraryFolderToStaging() error null libraryProject!!!\");\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tstring stageFolderRoot = @$\"{libraryStagingFolder}\\{libraryProject.Project.Package.Ident}\\\";\r\n\t\tstring libraryRootFolder = $\"{libraryProject.Project.RootDirectory.FullName}\";\r\n\r\n\t\tstring libraryCodeFolder = @$\"{libraryRootFolder}\\code\";\r\n\t\tstring libraryEditorFolder = @$\"{libraryRootFolder}\\Editor\";\r\n\t\tstring libraryAssetsFolder = @$\"{libraryRootFolder}\\Assets\";\r\n\t\tstring libraryUnitTestFolder = @$\"{libraryRootFolder}\\UnitTests\";\r\n\r\n\t\tstring targetCodeFolder = @$\"{libraryStagingFolder}\\{libraryProject.Project.Package.Ident}\\code\";\r\n\t\tstring targetEditorFolder = @$\"{libraryStagingFolder}\\{libraryProject.Project.Package.Ident}\\Editor\";\r\n\t\tstring targetAssetsFolder = @$\"{libraryStagingFolder}\\{libraryProject.Project.Package.Ident}\\Assets\";\r\n\r\n\t\tif (Directory.Exists(stageFolderRoot))\r\n\t\t{\r\n\t\t\tDirectory.Delete(stageFolderRoot, true);\r\n\t\t}\r\n\r\n\t\tCopyFiles(libraryCodeFolder, targetCodeFolder, true);\r\n\t\tCopyFiles(libraryEditorFolder, targetEditorFolder, true);\r\n\t\tCopyFiles(libraryAssetsFolder, targetAssetsFolder, true);\r\n\r\n\t\ttry\r\n\t\t{\r\n\t\t\t// I don't care about unit tests, so fuck em\r\n\t\t\tif (Directory.Exists(libraryUnitTestFolder))\r\n\t\t\t{\r\n\t\t\t\tDirectory.Delete(libraryUnitTestFolder, true);\r\n\t\t\t}\r\n\t\t}\r\n\t\tcatch\r\n\t\t{\r\n\t\t\tLog.Error(\"Could not delete libraryUnitTestFolder, you'll have to do it manually.\");\r\n\t\t}\r\n\r\n\t\timportedLibraries.packageIndentToStagedVersion[libraryProject.Project.Package.Ident] = libraryProject.Version.ToString();\r\n\t\timportedLibraries.Save();\r\n\t\t_ = FetchAndBuild();\r\n\t}\r\n\r\n\tvoid CopyFiles(string sourceFolder, string targetFolder, bool moveNotCopy = false)\r\n\t{\r\n\t\tvar relativePaths = GetRelativeFilePaths(sourceFolder, blacklistedFolders, blacklistedFileTypes);\r\n\r\n\t\t//Log.Info($\"CopyFiles() relativePaths: {relativePaths.Count()}\");\r\n\r\n\t\tforeach (var path in relativePaths)\r\n\t\t{\r\n\t\t\tvar sourcePath = @$\"{sourceFolder}\\{path}\";\r\n\t\t\tvar targetPath = @$\"{targetFolder}\\{path}\";\r\n\r\n\t\t\tvar targetDirectory = Path.GetDirectoryName(targetPath);\r\n\t\t\tif (!Directory.Exists(targetDirectory))\r\n\t\t\t{\r\n\t\t\t\tDirectory.CreateDirectory(targetDirectory);\r\n\t\t\t}\r\n\r\n\t\t\tif (moveNotCopy)\r\n\t\t\t{\r\n\t\t\t\tFile.Move(sourcePath, targetPath, true);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tFile.Copy(sourcePath, targetPath, true);\r\n\t\t\t}\r\n\r\n\t\t\t//Log.Info($\"{(moveNotCopy ? \"Move\" : \"Copy\")} sourcePath: {sourcePath} to targetPath: {targetPath}\");\r\n\t\t}\r\n\t}\r\n\r\n\tvoid DeleteImportedLibraryFolderFromProject(LibraryProject libraryProject)\r\n\t{\r\n\t\tif (Directory.Exists(projectCodeFolder))\r\n\t\t{\r\n\t\t\tDirectory.Delete(projectCodeFolder, true);\r\n\t\t}\r\n\t\tif (Directory.Exists(projectEditorFolder))\r\n\t\t{\r\n\t\t\tDirectory.Delete(projectEditorFolder, true);\r\n\t\t}\r\n\t\tif (Directory.Exists(projectAssetsFolder))\r\n\t\t{\r\n\t\t\tDirectory.Delete(projectAssetsFolder, true);\r\n\t\t}\r\n\r\n\t\tstring packageIdent = libraryProject.Project.Package.Ident;\r\n\t\tif (importedLibraries.packageIndentToProjectImportedVersion.ContainsKey(packageIdent))\r\n\t\t{\r\n\t\t\timportedLibraries.packageIndentToProjectImportedVersion.Remove(packageIdent);\r\n\t\t\timportedLibraries.Save();\r\n\t\t}\r\n\t\t_ = FetchAndBuild();\r\n\t}\r\n\r\n\tvoid DeleteImportedLibraryFolderFromLibrary()\r\n\t{\r\n\t\tif (Directory.Exists(selectedLibraryCodeFolder))\r\n\t\t{\r\n\t\t\tDirectory.Delete(selectedLibraryCodeFolder, true);\r\n\t\t}\r\n\t\tif (Directory.Exists(selectedLibraryEditorFolder))\r\n\t\t{\r\n\t\t\tDirectory.Delete(selectedLibraryEditorFolder, true);\r\n\t\t}\r\n\t\tif (Directory.Exists(selectedLibraryAssetsFolder))\r\n\t\t{\r\n\t\t\tDirectory.Delete(selectedLibraryAssetsFolder, true);\r\n\t\t}\r\n\r\n\t\tif (importedLibraries.packageIndentToLibraryImportedVersion.ContainsKey(importingLibraryToLibraryID))\r\n\t\t{\r\n\t\t\timportedLibraries.packageIndentToLibraryImportedVersion.Remove(importingLibraryToLibraryID);\r\n\t\t\timportedLibraries.Save();\r\n\t\t}\r\n\t\t_ = FetchAndBuild();\r\n\t}\r\n\r\n\tvoid DeleteImportedLibraryFolderInLibrary(string packageIndent)\r\n\t{\r\n\t\tstring folder = @$\"{selectedLibraryCodeFolder}\";\r\n\t\tstring folderEditor = @$\"{selectedLibraryEditorFolder}\";\r\n\r\n\t\tif (Directory.Exists(folder))\r\n\t\t{\r\n\t\t\tDirectory.Delete(folder, true);\r\n\t\t}\r\n\t\tif (Directory.Exists(folderEditor))\r\n\t\t{\r\n\t\t\tDirectory.Delete(folderEditor, true);\r\n\t\t}\r\n\r\n\t\tstring id = $\"{ComboLibrarySelection.Project.Package.Ident}-{Package.Ident}\";\r\n\t\tif (importedLibraries.packageIndentToLibraryImportedVersion.ContainsKey(id))\r\n\t\t{\r\n\t\t\timportedLibraries.packageIndentToLibraryImportedVersion.Remove(id);\r\n\t\t\timportedLibraries.Save();\r\n\t\t}\r\n\t}\r\n\r\n\tstatic IEnumerable<string> GetRelativeFilePaths(string rootDirectory, List<string> blacklistedFolders, List<string> blacklistedFileTypes)\r\n\t{\r\n\t\tvar filePaths = new List<string>();\r\n\r\n\t\tif (!Directory.Exists(rootDirectory))\r\n\t\t{\r\n\t\t\treturn filePaths;\r\n\t\t}\r\n\r\n\t\tforeach (var filePath in Directory.EnumerateFiles(rootDirectory, \"*.*\", SearchOption.AllDirectories))\r\n\t\t{\r\n\t\t\t// Check if the file is in a blacklisted folder\r\n\t\t\tvar directoryName = Path.GetDirectoryName(filePath);\r\n\t\t\tif (blacklistedFolders.Any(folder => directoryName.Contains(folder, StringComparison.OrdinalIgnoreCase)))\r\n\t\t\t{\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\t// Check if the file has a blacklisted extension\r\n\t\t\tvar fileExtension = Path.GetExtension(filePath);\r\n\t\t\tif (blacklistedFileTypes.Contains(fileExtension, StringComparer.OrdinalIgnoreCase))\r\n\t\t\t{\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\t// Get the relative path\r\n\t\t\tvar relativePath = Path.GetRelativePath(rootDirectory, filePath);\r\n\t\t\tfilePaths.Add(relativePath);\r\n\t\t}\r\n\r\n\t\treturn filePaths;\r\n\t}\r\n\r\n\tpublic string GetAssetsFolder(Project inProject, Package inPackage)\r\n\t{\r\n\t\tif (inProject == null || inPackage == null)\r\n\t\t\treturn null;\r\n\r\n\t\tstring folder = @$\"{inProject.GetAssetsPath()}\\libraries\\{inPackage.Ident}\";\r\n\t\treturn folder;\r\n\t}\r\n\r\n\tpublic string GetCodeFolder(Project inProject, Package inPackage)\r\n\t{\r\n\t\tif (inProject == null || inPackage == null)\r\n\t\t\treturn null;\r\n\r\n\t\tstring folder = @$\"{inProject.GetCodePath()}\\libraries\\{inPackage.Ident}\";\r\n\t\treturn folder;\r\n\t}\r\n\r\n\tpublic string GetEditorFolder(Project inProject, Package inPackage)\r\n\t{\r\n\t\tif (inProject == null || inPackage == null)\r\n\t\t\treturn null;\r\n\r\n\t\tstring folder = @$\"{inProject.GetRootPath()}\\Editor\\libraries\\{inPackage.Ident}\";\r\n\t\treturn folder;\r\n\t}\r\n\r\n\tprotected override Vector2 SizeHint()\r\n\t{\r\n\t\treturn new Vector2( 300, 100 );\r\n\t}\r\n}\r\n\r\npublic class ImportedLibraries\r\n{\r\n\tpublic Dictionary<string, string> packageIndentToProjectImportedVersion { get; set; } = new Dictionary<string, string>();\r\n\r\n\tpublic Dictionary<string, string> packageIndentToLibraryImportedVersion { get; set; } = new Dictionary<string, string>();\r\n\r\n\tpublic Dictionary<string, string> packageIndentToStagedVersion { get; set; } = new Dictionary<string, string>();\r\n\r\n\tstatic string libraryStagingFolder => $@\"{Project.Current.RootDirectory}\\staging\\libraryimporter\";\r\n\tstatic string saveFilePath => @$\"{libraryStagingFolder}\\imported_libraries.json\";\r\n\r\n\tpublic void Save()\r\n\t{\t\t\r\n\t\tvar json = JsonSerializer.Serialize(this, GetSerializerOptions());\r\n\t\tSystem.IO.File.WriteAllText(saveFilePath, json);\r\n\t}\r\n\r\n\tpublic static ImportedLibraries Load()\r\n\t{\r\n\t\tif (!System.IO.File.Exists(saveFilePath))\r\n\t\t{\r\n\t\t\treturn new ImportedLibraries();\r\n\t\t}\r\n\t\tvar fileContents = System.IO.File.ReadAllText(saveFilePath);\r\n\t\tvar inst = JsonSerializer.Deserialize<ImportedLibraries>(fileContents, GetSerializerOptions());\r\n\t\treturn inst;\r\n\t}\r\n\r\n\tpublic static JsonSerializerOptions GetSerializerOptions()\r\n\t{\r\n\t\tvar serializerOptions = new JsonSerializerOptions();\r\n\t\tserializerOptions.WriteIndented = true;\r\n\t\treturn serializerOptions;\r\n\t}\r\n}\r\n"
},
{
"Ident": "coomzy.libraryimporter",
"Path": "libraries/libevents/SortingHelper.cs",
"FileName": "SortingHelper.cs",
"PackageType": "library",
"CodeKind": "Game",
"AssetVersionId": 71858,
"IsPrivate": false,
"Code": "using System.Collections.Generic;\r\nusing System.Linq;\r\n\r\nnamespace Sandbox.Events;\r\n\r\n/// <summary>\r\n/// Generate an ordering based on a set of first-most and last-most items, and\r\n/// individual constraints between pairs of items. All first-most items will be\r\n/// ordered before all last-most items, and any other items will be put in the\r\n/// middle unless forced to be elsewhere by a constraint.\r\n/// </summary>\r\ninternal class SortingHelper\r\n{\r\n\tpublic record struct SortConstraint( int EarlierIndex, int LaterIndex )\r\n\t{\r\n\t\tpublic SortConstraint Complement => new ( LaterIndex, EarlierIndex );\r\n\t}\r\n\r\n\tprivate readonly int _itemCount;\r\n\r\n\tprivate readonly HashSet<SortConstraint> _initialConstraints = new HashSet<SortConstraint>();\r\n\r\n\tprivate readonly HashSet<int> _first = new HashSet<int>();\r\n\tprivate readonly HashSet<int> _last = new HashSet<int>();\r\n\r\n\tpublic SortingHelper( int itemCount )\r\n\t{\r\n\t\t_itemCount = itemCount;\r\n\t}\r\n\r\n\tpublic void AddConstraint( int earlierIndex, int laterIndex )\r\n\t{\r\n\t\t_initialConstraints.Add( new SortConstraint( earlierIndex, laterIndex ) );\r\n\t}\r\n\r\n\tpublic void AddFirst( int earlierIndex )\r\n\t{\r\n\t\t_first.Add( earlierIndex );\r\n\t}\r\n\r\n\tpublic void AddLast( int laterIndex )\r\n\t{\r\n\t\t_last.Add( laterIndex );\r\n\t}\r\n\r\n\tpublic bool Sort( List<int> result, out SortConstraint invalidConstraint )\r\n\t{\r\n\t\tvar middle = new HashSet<int>();\r\n\r\n\t\tfor ( var index = 0; index < _itemCount; ++index )\r\n\t\t{\r\n\t\t\tif ( !_first.Contains( index ) && !_last.Contains( index ) )\r\n\t\t\t\tmiddle.Add( index );\r\n\t\t}\r\n\r\n\t\tvar allConstraints = new HashSet<SortConstraint>();\r\n\t\tvar newConstraints = new Queue<SortConstraint>();\r\n\t\tvar beforeDict = new Dictionary<int, HashSet<int>>();\r\n\t\tvar afterDict = new Dictionary<int, HashSet<int>>();\r\n\r\n\t\tbool AddWorkingConstraint( int earlierIndex, int laterIndex, out SortConstraint constraint )\r\n\t\t{\r\n\t\t\tconstraint = new SortConstraint( earlierIndex, laterIndex );\r\n\r\n\t\t\tif ( allConstraints.Contains( constraint.Complement ) )\r\n\t\t\t\treturn false;\r\n\r\n\t\t\tif ( !allConstraints.Add( constraint ) )\r\n\t\t\t\treturn true;\r\n\r\n\t\t\tnewConstraints.Enqueue( constraint );\r\n\r\n\t\t\tif ( !beforeDict.TryGetValue( earlierIndex, out var before ) )\r\n\t\t\t\tbeforeDict.Add( earlierIndex, before = new HashSet<int>() );\r\n\r\n\t\t\tif ( !afterDict.TryGetValue( laterIndex, out var after ) )\r\n\t\t\t\tafterDict.Add( laterIndex, after = new HashSet<int>() );\r\n\r\n\t\t\tbefore.Add( laterIndex );\r\n\t\t\tafter.Add( earlierIndex );\r\n\r\n\t\t\treturn true;\r\n\t\t}\r\n\r\n\t\t// Add initial constraints\r\n\r\n\t\tforeach ( var initialConstraint in _initialConstraints )\r\n\t\t{\r\n\t\t\tif ( !AddWorkingConstraint( initialConstraint.EarlierIndex, initialConstraint.LaterIndex, out invalidConstraint ) )\r\n\t\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\t// Everything in _first should be before everything in _last\r\n\r\n\t\tforeach ( var earlierIndex in _first )\r\n\t\t{\r\n\t\t\tforeach ( var laterIndex in _last )\r\n\t\t\t{\r\n\t\t\t\tif ( !AddWorkingConstraint( earlierIndex, laterIndex, out invalidConstraint ) )\r\n\t\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Keep propagating constraints until nothing changes\r\n\r\n\t\twhile ( newConstraints.TryDequeue( out var nextConstraint ) )\r\n\t\t{\r\n\t\t\t// if a < b, and b < c, then a < c etc\r\n\r\n\t\t\tif ( beforeDict.TryGetValue( nextConstraint.LaterIndex, out var before ) )\r\n\t\t\t{\r\n\t\t\t\tforeach ( var laterIndex in before )\r\n\t\t\t\t{\r\n\t\t\t\t\tif ( !AddWorkingConstraint( nextConstraint.EarlierIndex, laterIndex, out invalidConstraint ) )\r\n\t\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif ( afterDict.TryGetValue( nextConstraint.EarlierIndex, out var after ) )\r\n\t\t\t{\r\n\t\t\t\tforeach ( var earlierIndex in after )\r\n\t\t\t\t{\r\n\t\t\t\t\tif ( !AddWorkingConstraint( earlierIndex, nextConstraint.LaterIndex, out invalidConstraint ) )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn false;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Now if we have any items that aren't using GroupOrder.First, and haven't\r\n\t\t// determined that they are ordered before another item with GroupOrder.First,\r\n\t\t// we can safely order them after all GroupOrder.First items. And vice versa.\r\n\r\n\t\tforeach ( var middleIndex in middle )\r\n\t\t{\r\n\t\t\tvar isBeforeAnyFirst = beforeDict.TryGetValue( middleIndex, out var before )\r\n\t\t\t\t&& before.Any( x => _first.Contains( x ) );\r\n\r\n\t\t\tvar isAfterAnyLast = afterDict.TryGetValue( middleIndex, out var after )\r\n\t\t\t\t&& after.Any( x => _last.Contains( x ) );\r\n\r\n\t\t\tif ( !isBeforeAnyFirst )\r\n\t\t\t{\r\n\t\t\t\tforeach ( var earlierIndex in _first )\r\n\t\t\t\t\tAddWorkingConstraint( earlierIndex, middleIndex, out invalidConstraint );\r\n\t\t\t}\r\n\r\n\t\t\tif ( !isAfterAnyLast )\r\n\t\t\t{\r\n\t\t\t\tforeach ( var laterIndex in _last )\r\n\t\t\t\t\tAddWorkingConstraint( middleIndex, laterIndex, out invalidConstraint );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Now lets add items to the final ordering if all items that should be sorted\r\n\t\t// before them are already added to that ordering. We'll implement this by choosing\r\n\t\t// items that have an empty list / don't appear in afterDict, and update that\r\n\t\t// dictionary as we go.\r\n\r\n\t\tvar earliestRemaining = new Queue<int>();\r\n\r\n\t\t// First, seed the queue with everything that's already not ordered after anything\r\n\r\n\t\tfor ( var index = 0; index < _itemCount; ++index )\r\n\t\t{\r\n\t\t\tif ( !afterDict.ContainsKey( index ) )\r\n\t\t\t{\r\n\t\t\t\tearliestRemaining.Enqueue( index );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tresult.Clear();\r\n\r\n\t\twhile ( earliestRemaining.TryDequeue( out var nextIndex ) )\r\n\t\t{\r\n\t\t\tresult.Add( nextIndex );\r\n\r\n\t\t\tforeach ( var laterIndex in beforeDict.TryGetValue( nextIndex, out var laterIndices )\r\n\t\t\t\t? laterIndices : Enumerable.Empty<int>() )\r\n\t\t\t{\r\n\t\t\t\tvar beforeLater = afterDict[laterIndex];\r\n\t\t\t\tbeforeLater.Remove( nextIndex );\r\n\r\n\t\t\t\tif ( beforeLater.Count == 0 )\r\n\t\t\t\t\tearliestRemaining.Enqueue( laterIndex );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tinvalidConstraint = default;\r\n\t\treturn result.Count == _itemCount;\r\n\t}\r\n}\r\n"
}
]
}