Added some workshop API members for Chunks

Currently OnItemCreated doesn't seem to get called
This commit is contained in:
James King 2017-08-07 17:15:20 +01:00
parent ba32817183
commit 524c687b83
7 changed files with 256 additions and 71 deletions

View File

@ -254,7 +254,7 @@ public void QueryCallback()
{ {
var gotCallback = false; var gotCallback = false;
Query.OnResult = ( q ) => Query.OnResult += ( q ) =>
{ {
Assert.AreEqual( q.Items.Length, 1 ); Assert.AreEqual( q.Items.Length, 1 );
Console.WriteLine( "Query.TotalResults: {0}", q.TotalResults ); Console.WriteLine( "Query.TotalResults: {0}", q.TotalResults );

View File

@ -27,8 +27,24 @@ internal RemoteStorage( Client c )
{ {
client = c; client = c;
native = client.native.remoteStorage; native = client.native.remoteStorage;
RemoteStoragePublishedFileSubscribed_t.RegisterCallback( c, onRemoteStoragePublishedFileSubscribed );
RemoteStoragePublishedFileUnsubscribed_t.RegisterCallback( c, onRemoteStoragePublishedFileUnsubscribed );
} }
private void onRemoteStoragePublishedFileSubscribed( RemoteStoragePublishedFileSubscribed_t value, bool ioFailure )
{
if ( ItemSubscribed != null && value.AppID == client.AppId ) ItemSubscribed( value.PublishedFileId );
}
private void onRemoteStoragePublishedFileUnsubscribed( RemoteStoragePublishedFileUnsubscribed_t value, bool ioFailure )
{
if ( ItemUnsubscribed != null && value.AppID == client.AppId ) ItemUnsubscribed( value.PublishedFileId );
}
public event Action<ulong> ItemSubscribed;
public event Action<ulong> ItemUnsubscribed;
/// <summary> /// <summary>
/// True if Steam Cloud is currently enabled by the current user. /// True if Steam Cloud is currently enabled by the current user.
/// </summary> /// </summary>

View File

@ -20,6 +20,7 @@ public class Editor
public string Folder { get; set; } = null; public string Folder { get; set; } = null;
public string PreviewImage { get; set; } = null; public string PreviewImage { get; set; } = null;
public List<string> Tags { get; set; } = new List<string>(); public List<string> Tags { get; set; } = new List<string>();
public Dictionary<string, string> KeyValueTags { get; set; } = new Dictionary<string, string>();
public bool Publishing { get; internal set; } public bool Publishing { get; internal set; }
public ItemType? Type { get; set; } public ItemType? Type { get; set; }
public string Error { get; internal set; } = null; public string Error { get; internal set; } = null;
@ -36,60 +37,63 @@ public enum VisibilityType : int
public bool NeedToAgreeToWorkshopLegal { get; internal set; } public bool NeedToAgreeToWorkshopLegal { get; internal set; }
public event Action<Editor> PublishSucceeded;
public event Action<Editor> PublishFailed;
private PublishStatus _publishStatus;
public PublishStatus PublishStatus
{
get
{
UpdatePublishProgress();
return _publishStatus;
}
}
public double Progress public double Progress
{ {
get get
{ {
if ( !Publishing ) return 1.0; if ( CreateItem != null ) return 0d;
if ( CreateItem != null ) return 0.0; if ( SubmitItemUpdate == null ) return 1d;
if ( SubmitItemUpdate == null ) return 1.0; if ( !Publishing ) return 1d;
ulong b = 0; UpdatePublishProgress();
ulong t = 0; return _bytesTotal > 0 ? _bytesUploaded / (double) _bytesTotal : 0d;
workshop.steamworks.native.ugc.GetItemUpdateProgress( UpdateHandle, out b, out t );
if ( t == 0 )
return 0;
return (double)b / (double) t;
} }
} }
private ulong _bytesUploaded;
public int BytesUploaded public int BytesUploaded
{ {
get get
{ {
if ( !Publishing ) return 0; UpdatePublishProgress();
if ( CreateItem != null ) return 0; return (int) _bytesUploaded;
if ( SubmitItemUpdate == null ) return 0;
ulong b = 0;
ulong t = 0;
workshop.steamworks.native.ugc.GetItemUpdateProgress( UpdateHandle, out b, out t );
return (int) b;
} }
} }
private ulong _bytesTotal;
public int BytesTotal public int BytesTotal
{ {
get get
{ {
if ( !Publishing ) return 0; UpdatePublishProgress();
if ( CreateItem != null ) return 0; return (int) _bytesTotal;
if ( SubmitItemUpdate == null ) return 0;
ulong b = 0;
ulong t = 0;
workshop.steamworks.native.ugc.GetItemUpdateProgress( UpdateHandle, out b, out t );
return (int)t;
} }
} }
private void UpdatePublishProgress()
{
if ( SubmitItemUpdate == null )
{
_publishStatus = PublishStatus.Invalid;
return;
}
_publishStatus = (PublishStatus) workshop.steamworks.native.ugc.GetItemUpdateProgress( UpdateHandle, out _bytesUploaded, out _bytesTotal );
}
public void Publish() public void Publish()
{ {
Publishing = true; Publishing = true;
@ -109,13 +113,17 @@ private void StartCreatingItem()
if ( !Type.HasValue ) if ( !Type.HasValue )
throw new System.Exception( "Editor.Type must be set when creating a new item!" ); throw new System.Exception( "Editor.Type must be set when creating a new item!" );
System.Diagnostics.Debug.WriteLine( "StartCreatingItem()" );
CreateItem = workshop.ugc.CreateItem( workshop.steamworks.AppId, (SteamNative.WorkshopFileType)(uint)Type, OnItemCreated ); CreateItem = workshop.ugc.CreateItem( workshop.steamworks.AppId, (SteamNative.WorkshopFileType)(uint)Type, OnItemCreated );
} }
private void OnItemCreated( SteamNative.CreateItemResult_t obj, bool Failed ) private void OnItemCreated( SteamNative.CreateItemResult_t obj, bool Failed )
{ {
System.Diagnostics.Debug.WriteLine( $"OnItemCreated({obj.PublishedFileId}, {Failed})" );
NeedToAgreeToWorkshopLegal = obj.UserNeedsToAcceptWorkshopLegalAgreement; NeedToAgreeToWorkshopLegal = obj.UserNeedsToAcceptWorkshopLegalAgreement;
CreateItem.Dispose(); CreateItem.Dispose();
CreateItem = null;
if ( obj.Result == SteamNative.Result.OK && !Failed ) if ( obj.Result == SteamNative.Result.OK && !Failed )
{ {
@ -126,6 +134,8 @@ private void OnItemCreated( SteamNative.CreateItemResult_t obj, bool Failed )
Error = "Error creating new file: " + obj.Result.ToString() + "("+ obj.PublishedFileId+ ")"; Error = "Error creating new file: " + obj.Result.ToString() + "("+ obj.PublishedFileId+ ")";
Publishing = false; Publishing = false;
PublishFailed?.Invoke( this );
} }
private void PublishChanges() private void PublishChanges()
@ -151,6 +161,14 @@ private void PublishChanges()
if ( Tags != null && Tags.Count > 0 ) if ( Tags != null && Tags.Count > 0 )
workshop.ugc.SetItemTags( UpdateHandle, Tags.ToArray() ); workshop.ugc.SetItemTags( UpdateHandle, Tags.ToArray() );
if ( KeyValueTags != null )
{
foreach ( var keyValue in KeyValueTags )
{
workshop.ugc.AddItemKeyValueTag( UpdateHandle, keyValue.Key, keyValue.Value );
}
}
if ( Visibility.HasValue ) if ( Visibility.HasValue )
workshop.ugc.SetItemVisibility( UpdateHandle, (SteamNative.RemoteStoragePublishedFileVisibility)(uint)Visibility.Value ); workshop.ugc.SetItemVisibility( UpdateHandle, (SteamNative.RemoteStoragePublishedFileVisibility)(uint)Visibility.Value );
@ -185,7 +203,10 @@ private void PublishChanges()
private void OnChangesSubmitted( SteamNative.SubmitItemUpdateResult_t obj, bool Failed ) private void OnChangesSubmitted( SteamNative.SubmitItemUpdateResult_t obj, bool Failed )
{ {
if ( Failed ) if ( Failed )
throw new System.Exception( "CreateItemResult_t Failed" ); {
if ( PublishFailed != null ) PublishFailed( this );
else throw new System.Exception( "CreateItemResult_t Failed" );
}
SubmitItemUpdate = null; SubmitItemUpdate = null;
NeedToAgreeToWorkshopLegal = obj.UserNeedsToAcceptWorkshopLegalAgreement; NeedToAgreeToWorkshopLegal = obj.UserNeedsToAcceptWorkshopLegalAgreement;
@ -193,10 +214,12 @@ private void OnChangesSubmitted( SteamNative.SubmitItemUpdateResult_t obj, bool
if ( obj.Result == SteamNative.Result.OK ) if ( obj.Result == SteamNative.Result.OK )
{ {
PublishSucceeded?.Invoke( this );
return; return;
} }
Error = "Error publishing changes: " + obj.Result.ToString() + " ("+ NeedToAgreeToWorkshopLegal + ")"; Error = "Error publishing changes: " + obj.Result.ToString() + " ("+ NeedToAgreeToWorkshopLegal + ")";
PublishFailed?.Invoke( this );
} }
public void Delete() public void Delete()
@ -205,5 +228,15 @@ public void Delete()
Id = 0; Id = 0;
} }
} }
public enum PublishStatus : int
{
Invalid = 0,
PreparingConfig = 1,
PreparingContent = 2,
UploadingContent = 3,
UploadingPreviewFile = 4,
CommittingChanges = 5,
}
} }
} }

View File

@ -48,19 +48,20 @@ internal static Item From( SteamNative.SteamUGCDetails_t details, Workshop works
return item; return item;
} }
public void Download( bool highPriority = true ) public bool Download( bool highPriority = true )
{ {
if ( Installed ) return; if ( Installed ) return false;
if ( Downloading ) return; if ( Downloading ) return false;
if ( !workshop.ugc.DownloadItem( Id, highPriority ) ) if ( !workshop.ugc.DownloadItem( Id, highPriority ) )
{ {
Console.WriteLine( "Download Failed" ); return false;
return;
} }
workshop.OnFileDownloaded += OnFileDownloaded; workshop.OnFileDownloaded += OnFileDownloaded;
workshop.OnItemInstalled += OnItemInstalled; workshop.OnItemInstalled += OnItemInstalled;
return true;
} }
public void Subscribe() public void Subscribe()
@ -109,8 +110,7 @@ public double DownloadProgress
public bool Subscribed { get { return ( State & ItemState.Subscribed ) != 0; } } public bool Subscribed { get { return ( State & ItemState.Subscribed ) != 0; } }
public bool NeedsUpdate { get { return ( State & ItemState.NeedsUpdate ) != 0; } } public bool NeedsUpdate { get { return ( State & ItemState.NeedsUpdate ) != 0; } }
private SteamNative.ItemState State { get { return ( SteamNative.ItemState) workshop.ugc.GetItemState( Id ); } } public ItemState State { get { return (ItemState) workshop.ugc.GetItemState( Id ); } }
private DirectoryInfo _directory; private DirectoryInfo _directory;
@ -118,33 +118,55 @@ public DirectoryInfo Directory
{ {
get get
{ {
if ( _directory != null ) UpdateInstallInfo();
return _directory;
if ( !Installed )
return null;
ulong sizeOnDisk;
string folder;
uint timestamp;
if ( workshop.ugc.GetItemInstallInfo( Id, out sizeOnDisk, out folder, out timestamp ) )
{
_directory = new DirectoryInfo( folder );
Size = sizeOnDisk;
if ( !_directory.Exists )
{
// Size = 0;
// _directory = null;
}
}
return _directory; return _directory;
} }
} }
public ulong Size { get; private set; } private ulong _size;
public ulong Size
{
get
{
UpdateInstallInfo();
return _size;
}
}
private DateTime _timestamp;
public DateTime Timestamp
{
get
{
UpdateInstallInfo();
return _timestamp;
}
}
internal void UpdateInstallInfo()
{
if ( _directory != null ) return;
if ( !Installed ) return;
ulong sizeOnDisk;
string folder;
uint timestamp;
if ( workshop.ugc.GetItemInstallInfo( Id, out sizeOnDisk, out folder, out timestamp ) )
{
_directory = new DirectoryInfo( folder );
_size = sizeOnDisk;
_timestamp = Utility.Epoch.ToDateTime( timestamp );
if ( !_directory.Exists )
{
// Size = 0;
// _directory = null;
}
}
}
private ulong _BytesDownloaded, _BytesTotal; private ulong _BytesDownloaded, _BytesTotal;
@ -227,5 +249,17 @@ public string OwnerName
} }
} }
} }
[Flags]
public enum ItemState
{
None = 0,
Subscribed = 1,
LegacyItem = 2,
Installed = 4,
NeedsUpdate = 8,
Downloading = 16,
DownloadPending = 32
}
} }
} }

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using SteamNative;
namespace Facepunch.Steamworks namespace Facepunch.Steamworks
{ {
@ -44,9 +45,15 @@ public class Query : IDisposable
public UserQueryType UserQueryType { get; set; } = UserQueryType.Published; public UserQueryType UserQueryType { get; set; } = UserQueryType.Published;
/// <summary> /// <summary>
/// Called when the query finishes /// Called when the query succeeds
/// </summary> /// </summary>
public Action<Query> OnResult; public event Action<Query> OnResult;
/// <summary>
/// Called when the query fails. An exception will be thrown on IO failure
/// if no callbacks are added to this event.
/// </summary>
public event Action<Query, Callbacks.Result> OnFailure;
/// <summary> /// <summary>
/// Page starts at 1 !! /// Page starts at 1 !!
@ -105,15 +112,27 @@ unsafe void RunInternal()
if ( !string.IsNullOrEmpty( SearchText ) ) if ( !string.IsNullOrEmpty( SearchText ) )
workshop.ugc.SetSearchText( Handle, SearchText ); workshop.ugc.SetSearchText( Handle, SearchText );
foreach ( var tag in RequireTags ) if ( RequireTags != null )
workshop.ugc.AddRequiredTag( Handle, tag ); {
foreach ( var tag in RequireTags )
workshop.ugc.AddRequiredTag( Handle, tag );
if ( RequireTags.Count > 0 ) if ( RequireTags.Count > 0 )
workshop.ugc.SetMatchAnyTag( Handle, !RequireAllTags ); workshop.ugc.SetMatchAnyTag( Handle, !RequireAllTags );
}
if ( RequireKeyValueTags != null )
{
foreach ( var keyValue in RequireKeyValueTags )
workshop.ugc.AddRequiredKeyValueTag( Handle, keyValue.Key, keyValue.Value );
}
if ( RankedByTrendDays > 0 ) if ( RankedByTrendDays > 0 )
workshop.ugc.SetRankedByTrendDays( Handle, (uint) RankedByTrendDays ); workshop.ugc.SetRankedByTrendDays( Handle, (uint) RankedByTrendDays );
if ( MaxCachedAge > TimeSpan.Zero )
workshop.ugc.SetAllowCachedResponse( Handle, (uint) MaxCachedAge.TotalSeconds );
foreach ( var tag in ExcludeTags ) foreach ( var tag in ExcludeTags )
workshop.ugc.AddExcludedTag( Handle, tag ); workshop.ugc.AddExcludedTag( Handle, tag );
@ -122,8 +141,14 @@ unsafe void RunInternal()
void ResultCallback( SteamNative.SteamUGCQueryCompleted_t data, bool bFailed ) void ResultCallback( SteamNative.SteamUGCQueryCompleted_t data, bool bFailed )
{ {
if ( bFailed ) if ( bFailed || OnFailure != null && data.Result != Result.OK )
throw new System.Exception( "bFailed!" ); {
// This used to always throw (before OnFailure was added), so for backwards
// compatibility it will still throw if OnFailure isn't subscribed to.
if ( OnFailure != null ) OnFailure( this, bFailed ? Callbacks.Result.IOFailure : (Callbacks.Result) data.Result );
else if ( bFailed ) throw new System.Exception( "bFailed!" );
}
var gotFiles = 0; var gotFiles = 0;
for ( int i = 0; i < data.NumResultsReturned; i++ ) for ( int i = 0; i < data.NumResultsReturned; i++ )
@ -177,6 +202,7 @@ void ResultCallback( SteamNative.SteamUGCQueryCompleted_t data, bool bFailed )
else else
{ {
Items = _results.ToArray(); Items = _results.ToArray();
IsCachedData = data.CachedData;
if ( OnResult != null ) if ( OnResult != null )
{ {
@ -199,11 +225,18 @@ public bool IsRunning
get { return Callback != null; } get { return Callback != null; }
} }
public bool IsCachedData { get; private set; }
/// <summary> /// <summary>
/// Only return items with these tags /// Only return items with these tags
/// </summary> /// </summary>
public List<string> RequireTags { get; set; } = new List<string>(); public List<string> RequireTags { get; set; } = new List<string>();
/// <summary>
/// Only return items with these key-value tags
/// </summary>
public Dictionary<string, string> RequireKeyValueTags { get; set; } = new Dictionary<string, string>();
/// <summary> /// <summary>
/// If true, return items that have all RequireTags /// If true, return items that have all RequireTags
/// If false, return items that have any tags in RequireTags /// If false, return items that have any tags in RequireTags
@ -220,7 +253,10 @@ public bool IsRunning
/// </summary> /// </summary>
public List<ulong> FileId { get; set; } = new List<ulong>(); public List<ulong> FileId { get; set; } = new List<ulong>();
/// <summary>
/// Maximum age of a previously cached item. Zero by default, so items are not cached.
/// </summary>
public TimeSpan MaxCachedAge { get; set; } = TimeSpan.Zero;
/// <summary> /// <summary>
/// Don't call this in production! /// Don't call this in production!

View File

@ -1,5 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using SteamNative; using SteamNative;
namespace Facepunch.Steamworks namespace Facepunch.Steamworks
@ -40,6 +43,12 @@ public partial class Workshop : IDisposable
/// </summary> /// </summary>
public event Action<ulong> OnItemInstalled; public event Action<ulong> OnItemInstalled;
/// <summary>
/// Gets the total number of items the current user is subscribed to for
/// the game or application. Returns 0 if called from a game server.
/// </summary>
public int NumSubscribedItems => (int) ugc.GetNumSubscribedItems();
internal Workshop( BaseSteamworks steamworks, SteamNative.SteamUGC ugc, SteamNative.SteamRemoteStorage remoteStorage ) internal Workshop( BaseSteamworks steamworks, SteamNative.SteamUGC ugc, SteamNative.SteamRemoteStorage remoteStorage )
{ {
this.ugc = ugc; this.ugc = ugc;
@ -76,6 +85,50 @@ private void onDownloadResult( SteamNative.DownloadItemResult_t obj, bool failed
OnFileDownloaded( obj.PublishedFileId, (Callbacks.Result) obj.Result ); OnFileDownloaded( obj.PublishedFileId, (Callbacks.Result) obj.Result );
} }
[ThreadStatic]
private static ulong[] _sItemBuffer;
/// <summary>
/// Gets a list of all of the items the current user is subscribed to for the current game. The
/// IDs for each item are written into <paramref name="destIds"/>.
/// </summary>
/// <param name="destIds">
/// Destination array to write the item IDs to. Should typically be large enough to fit
/// <see cref="NumSubscribedItems"/>.</param>
/// <returns>
/// The number of subscribed workshop items that were populated into <paramref name="destIds"/>.
/// </returns>
public unsafe int GetSubscribedItems( ulong[] destIds )
{
var count = Math.Min( destIds.Length, NumSubscribedItems );
fixed ( ulong* ptr = destIds )
{
ugc.GetSubscribedItems( (PublishedFileId_t*) ptr, (uint) count );
}
return count;
}
/// <summary>
/// Gets a list of all of the items the current user is subscribed to for the current game. The
/// IDs for each item are appended to <paramref name="destIds"/>.
/// </summary>
/// <param name="destIds">
/// Destination list to append the item IDs to.</param>
/// <returns>
/// The number of subscribed workshop items that were populated into <paramref name="destIds"/>.
/// </returns>
public int GetSubscribedItems( List<ulong> destIds )
{
var items = Utility.EnsureBufferCapacity( ref _sItemBuffer, NumSubscribedItems );
var count = GetSubscribedItems( items );
destIds.AddRange( items.Take( count ) );
return count;
}
/// <summary> /// <summary>
/// Creates a query object, which is used to get a list of items. /// Creates a query object, which is used to get a list of items.
/// ///

View File

@ -15,6 +15,19 @@ static internal uint SwapBytes( uint x )
( ( x & 0xff000000 ) >> 24 ); ( ( x & 0xff000000 ) >> 24 );
} }
static internal int NextPowerOf2( int x )
{
var po2 = 1;
while ( po2 < x ) po2 <<= 1;
return po2;
}
static internal T[] EnsureBufferCapacity<T>( ref T[] buffer, int size )
{
if ( buffer == null || buffer.Length < size ) buffer = new T[NextPowerOf2( size )];
return buffer;
}
static internal class Epoch static internal class Epoch
{ {
private static readonly DateTime epoch = new DateTime( 1970, 1, 1, 0, 0, 0, DateTimeKind.Utc ); private static readonly DateTime epoch = new DateTime( 1970, 1, 1, 0, 0, 0, DateTimeKind.Utc );