Files
sbox-public/engine/Sandbox.Tools/ToolsDll.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

309 lines
8.8 KiB
C#

using NativeEngine;
using Sandbox.ActionGraphs;
using Sandbox.Audio;
using Sandbox.Engine;
using Sandbox.Internal;
using System;
namespace Sandbox;
internal class ToolsDll : IToolsDll
{
static Logger log = new Logger( "ToolsDll" );
PackageLoader.Enroller AssemblyEnroller { get; set; }
public ToolsDll()
{
Global.Assembly = GetType().Assembly;
}
public void Bootstrap()
{
EditorEvent.Init();
EditorEvent.RegisterAssembly( Global.Assembly );
EditorTypeLibrary = new Sandbox.Internal.TypeLibrary();
EditorTypeLibrary.AddIntrinsicTypes();
EditorTypeLibrary.AddAssembly( typeof( Vector3 ).Assembly, false );
EditorTypeLibrary.AddAssembly( Global.Assembly, true ); // editor can access everything anyway
EditorTypeLibrary.AddAssembly( typeof( EngineLoop ).Assembly, false );
EditorTypeLibrary.AddAssembly( typeof( Facepunch.ActionGraphs.ActionGraph ).Assembly, false );
Sandbox.Internal.TypeLibrary.Editor = EditorTypeLibrary;
Sandbox.Generator.Processor.DefaultPackageAssetResolver = ResolvePackageAsset;
EditorCookie = new CookieContainer( "tools" );
ActionGraphDebugger.Enabled = true;
// make sure mixer is initialized at least once
Mixer.ResetToDefault();
EditorShortcuts.RegisterShortcuts();
}
public void Exiting()
{
EditorEvent.Run( "app.exit" );
EditorCookie?.Save();
ProjectCookie?.Save();
}
public void RunEvent( string name ) => EditorEvent.Run( name );
public void RunEvent( string name, object argument ) => EditorEvent.Run( name, argument );
public void RunEvent( string name, object arg0, object arg1 ) => EditorEvent.Run( name, arg0, arg1 );
public void RunEvent<T>( Action<T> action ) => EditorEvent.RunInterface<T>( action );
/// <summary>
/// Called from <see cref="EngineLoop.OnClientOutput"/>,
/// this method exists because we can't access the scene editor session from GameMenuDll to render cameras in the game viewport FROM the editor scene
/// and we probably don't want to either
/// </summary>
public void OnRender()
{
SceneRenderingWidget.RenderAll();
}
public bool ConsoleFocus()
{
EditorWindow.ConsoleFocus();
return true;
}
public void ExitPlaymode()
{
if ( !Game.IsPlaying )
return;
EditorScene.Stop();
}
/// <summary>
/// Bootstrapping has finished. Close the splash screen and show the editor.
/// </summary>
public async Task Initialize()
{
// Associate .sbproj with the editor
FileAssociations.Create();
Log.Info( "Compiling base projects" );
//
// Compile our base projects first
//
if ( !await Project.CompileAsync() )
{
// If we can't compile base projects, bail - everything will be fucked
// Doesn't need to be descriptive because it's either we've fucked it, or the user has edited base files and fucked it.
throw new System.Exception( "Base projects failed to compile. Can't continue. (Check log file)" );
}
// Rebuild content path now because some projects are added during bootstrap
Editor.FileSystem.RebuildContentPath();
Editor.Application.ReloadStyles();
Assert.NotNull( GameInstanceDll.PackageLoader );
Assert.NotNull( GlobalGameNamespace.TypeLibrary );
Sandbox.GameInstanceDll.PackageLoader.ToolsMode = true;
AssemblyEnroller = Sandbox.GameInstanceDll.PackageLoader.CreateEnroller( "tools" );
Sandbox.GameInstanceDll.PackageLoader.HotloadWatch( GetType().Assembly );
Sandbox.GameInstanceDll.PackageLoader.OnAfterHotload = () =>
{
// here we're overriding what the GameMenu set,
// so we're gonna do that stuff too.
GlobalContext.Current.OnHotload();
foreach ( var session in SceneEditorSession.All )
{
session.Scene.OnHotload();
}
Event.Run( "hotloaded" );
// editor stuff
EditorEvent.Run( "hotloaded" );
};
AssemblyEnroller.OnAssemblyAdded = ( assembly ) =>
{
Log.Trace( $"Tools - Loading Assembly {assembly.Assembly} ({assembly.IsEditorAssembly})" );
if ( assembly.IsEditorAssembly )
{
EditorEvent.RegisterAssembly( assembly.Assembly );
ConVarSystem.AddAssembly( assembly.Assembly, $"editor.{assembly.Name.Replace( ".editor", "" )}" );
Sandbox.Mounting.Directory.AddAssembly( assembly.Assembly );
}
EditorTypeLibrary.AddAssembly( assembly.Assembly, true );
GameData.AddAssembly( assembly.Assembly );
if ( !StartupLoadProject.IsLoading )
{
// Can't do this during bootstrapping in case a resource references a
// type that hasn't been loaded yet.
if ( AssetType.UpdateCustomTypes() )
{
// If the types changed, re-import all the custom types from disk
AssetType.ImportCustomTypeFiles();
}
}
};
AssemblyEnroller.OnAssemblyRemoved = ( outgoing ) =>
{
if ( outgoing.IsEditorAssembly )
{
EditorEvent.UnregisterAssembly( outgoing.Assembly );
ConVarSystem.RemoveAssembly( outgoing.Assembly );
Sandbox.Mounting.Directory.RemoveAssembly( outgoing.Assembly );
}
EditorTypeLibrary.RemoveAssembly( outgoing.Assembly );
GameData.RemoveAssembly( outgoing.Assembly );
ManagedTools.AssembliesDirty = true;
};
GameResourceProcessor.Initialize();
PackageManager.OnPackageInstalledToContext += OnPackageInstalled;
// Hammer entity definitions: prop_static, etc.
GameData.AddAssembly( Global.Assembly );
GameData.AddAssembly( typeof( Model ).Assembly );
//
// Add all game addons to be compiled in tools mode, making them accessible for Hammer, Asset Editor, etc.
//
ManagedTools.AssembliesDirty = true;
}
/// <summary>
/// Load the startup project for the first time
/// </summary>
public async Task LoadProject()
{
await StartupLoadProject.Run();
}
/// <summary>
/// Called from the code generator, the job of this function is:
/// 1. Make sure the package is downloaded and installed
/// 2. Return the relative path to the primary asset
/// </summary>
string ResolvePackageAsset( string packageName )
{
Package package = Package.FetchAsync( packageName, false ).Result;
if ( package == null )
{
return null;
}
if ( StartupLoadProject.IsLoading )
{
StartupLoadProject.QueuePackageDownload( packageName );
}
else
{
MainThread.Queue( () => AssetSystem.InstallAsync( package ).Wait() );
}
return package?.PrimaryAsset;
}
private void OnPackageInstalled( PackageManager.ActivePackage package, string context )
{
log.Trace( $"OnPackageInstalled: {package.Package.FullIdent} {context}" );
// only load if a tools context
if ( context != "tools" && context != "hammer" && package.Package is not LocalPackage ) return;
log.Trace( $" - Loading: {package.Package.FullIdent}" );
// make sure it's loaded into our context
AssemblyEnroller.LoadPackage( package.Package.FullIdent );
}
public void OnFunctionKey( ButtonCode key, KeyboardModifiers modifiers )
{
var keys = NativeEngine.InputSystem.CodeToString( key ).ToUpperInvariant();
if ( modifiers.HasFlag( KeyboardModifiers.Shift ) ) keys = "SHIFT+" + keys;
if ( modifiers.HasFlag( KeyboardModifiers.Alt ) ) keys = "ALT+" + keys;
if ( modifiers.HasFlag( KeyboardModifiers.Ctrl ) ) keys = "CTRL+" + keys;
EditorShortcuts.Invoke( keys, true );
// This is really just here for F5, if we stop the game session and defocus the game window, Qt is going to then run it's event
// The other option is, do we need to run EditorShortcuts from game mode? What is there other than F5?
EditorShortcuts._timeSinceGlobalShortcut = 0;
}
public void Spin()
{
BindSystem?.Tick();
g_pToolFramework2.Spin();
EngineLoop.RunAsyncTasks();
NativeEngine.EngineGlobal.ToolsStallMonitor_IndicateActivity();
}
public void Tick()
{
// close progress windows if compile has finished
CompileStatus.CloseProgress();
// Make sure we agree about Time.Now during editor events while in play mode
if ( Game.IsPlaying && Game.ActiveScene is { } scene )
{
Time.Update( scene.TimeNow, scene.TimeDelta );
}
// Escape was pressed in game and wasn't swallowed
// so lets change focus from the game window to the main editor
// window, which is going to free the mouse cursor from being captured
if ( Game.IsPlaying && Input.EscapePressed )
{
EditorWindow.Focus();
Input.EscapePressed = false;
}
}
/// <summary>
/// Registers exclusive Sandbox.Tools <see cref="Sandbox.IHandle"/> types
/// </summary>
public int RegisterHandle( IntPtr ptr, uint type ) => HandleTypes.RegisterHandle( ptr, type );
public object InspectedObject
{
get => EditorUtility.InspectorObject;
set => EditorUtility.InspectorObject = value;
}
/// <summary>
/// Return true if the GameFrame is visible
/// </summary>
[Obsolete]
public bool IsGameViewVisible => false;
public async Task OnInitializeHost()
{
await CloudAsset.AddNewServerPackages();
}
void IToolsDll.RunFrame()
{
using ( Sandbox.Diagnostics.PerformanceStats.Timings.Editor.Scope() )
{
g_pToolFramework2.Tools_RunFrame();
g_pToolFramework2.Tools_OnIdle( 0 );
g_pToolFramework2.Tools_UnloadPending();
}
}
}