Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,12 @@ public class PackageReader
public PackageReader(string vpePath)
{
_vpePath = vpePath;
ContentResolver = new PackagedContentResolver(vpePath);
}

/// <summary>Resolves packaged content through the same cache path used by Player builds.</summary>
public IPackagedContentResolver ContentResolver { get; }

public async Task ImportIntoScene(string tableName)
{
var sw = new Stopwatch();
Expand Down Expand Up @@ -91,6 +95,10 @@ public async Task ImportIntoScene(string tableName)
comp?.UnpackReferences(stream.GetData(), _tableRoot, _refs, _files);
});

foreach (var consumer in _table.GetComponentsInChildren<MonoBehaviour>(true).OfType<IPackagedContentConsumer>()) {
consumer.SetPackagedContentResolver(ContentResolver);
}

ReadGlobals();
ReadTableMetadata();
SimulationThreadComponent.EnsureForTable(_tableRoot.gameObject);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

namespace VisualPinball.Unity.Editor
{
public class PackageWriter
public class PackageWriter : IPackagedContentResolver
{
private readonly GameObject _table;
private readonly PackagedRefs _refs;
Expand All @@ -53,6 +53,7 @@ public class PackageWriter
private IReadOnlyDictionary<string, GlbImageSwap.ImageReplacement> _originalGltfImageReplacements;
private Dictionary<Transform, string> _nodeIdByTransform;
private readonly SortedSet<string> _usedTypeNames = new(StringComparer.Ordinal);
private readonly List<PackagedContentWriter.PreparedContent> _contentBundles = new();

private const bool ExportActivesOnly = true;

Expand Down Expand Up @@ -90,6 +91,22 @@ public Task WritePackageAsync(string path, IProgress<ExportProgress> progress =
return WritePackage(path, progress, cancellationToken);
}

/// <summary>
/// Adds an inert directory to the next package written by this writer. The directory is
/// scanned immediately so its reference can be serialized by table components.
/// </summary>
public PackagedContentRef AddDirectory(string kind, string sourceRoot, ContentPackOptions options = null)
{
var prepared = PackagedContentWriter.PrepareDirectory(kind, sourceRoot, options);
_contentBundles.Add(prepared);
return prepared.Reference;
}

public Task<string> ResolveAsync(PackagedContentRef contentRef, IProgress<float> progress, CancellationToken ct)
{
throw new NotSupportedException("PackageWriter only creates content bundles. Resolve with PackageReader.ContentResolver or RuntimePackageReader.ContentResolver.");
}

private async Task WritePackage(string path, IProgress<ExportProgress> progress, CancellationToken ct)
{
var sw = new Stopwatch();
Expand All @@ -107,6 +124,7 @@ private async Task WritePackage(string path, IProgress<ExportProgress> progress,
storage = PackageApi.StorageManager.CreateStorage(path);

_tableFolder = storage.AddFolder(PackageApi.TableFolder);
var contentWriter = new PackagedContentWriter(_tableFolder);
_globalFolder = _tableFolder.AddFolder(PackageApi.GlobalFolder);
_metaFolder = _tableFolder.AddFolder(PackageApi.MetaFolder);
_files = new PackagedFiles(_tableFolder, _refs);
Expand All @@ -122,6 +140,16 @@ private async Task WritePackage(string path, IProgress<ExportProgress> progress,
_nodeIdByTransform = VpeNodeIds.AssignIds(_table.transform);
_refs.SetNodeIdsForWrite(_nodeIdByTransform);

// Content sources must receive their stable refs before component data is serialized.
foreach (var source in _table.GetComponentsInChildren<MonoBehaviour>(true).OfType<IPackagedContentSource>()) {
ct.ThrowIfCancellationRequested();
source.PreparePackagedContent(this);
}
foreach (var bundle in _contentBundles.OrderBy(item => item.Reference.Id, StringComparer.Ordinal)) {
ct.ThrowIfCancellationRequested();
contentWriter.Write(bundle);
}

// prepare scene data
ct.ThrowIfCancellationRequested();
Report(progress, 0.03f, "Preparing scene…");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Visual Pinball Engine
// Copyright (C) 2026 freezy and VPE Team

using UnityEditor;
using UnityEngine;

namespace VisualPinball.Unity.Editor
{
[CustomPropertyDrawer(typeof(PackagedContentRef))]
public sealed class PackagedContentRefDrawer : PropertyDrawer
{
private static readonly string[] Fields = {
"Kind", "EntryPoint", "Id", "ContentHash", "FileCount", "TotalBytes", "SourceDirectory", "ValidationStatus"
};

public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
if (!property.isExpanded) {
return EditorGUIUtility.singleLineHeight;
}
return (Fields.Length + 1) * (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
}

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
var line = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
property.isExpanded = EditorGUI.Foldout(line, property.isExpanded, label, true);
if (property.isExpanded) {
EditorGUI.indentLevel++;
using (new EditorGUI.DisabledScope(true)) {
foreach (var fieldName in Fields) {
line.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
var child = property.FindPropertyRelative(fieldName);
if (child == null) {
continue;
}
if (fieldName == "TotalBytes") {
EditorGUI.TextField(line, ObjectNames.NicifyVariableName(fieldName), EditorUtility.FormatBytes(child.longValue));
} else {
EditorGUI.PropertyField(line, child, new GUIContent(ObjectNames.NicifyVariableName(fieldName)));
}
}
}
EditorGUI.indentLevel--;
}
EditorGUI.EndProperty();
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading