Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LBP3 Playlists #702

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
79b4ef0
WIP LBP3 Playlist support
Toastbrot236 Nov 27, 2024
e3c8ef4
Rename lbp1 playlist related things to be explicitly lbp1 related to …
Toastbrot236 Nov 29, 2024
c2494d8
bug fixes, 1 more endpoint, some more experiments, some other things
Toastbrot236 Nov 29, 2024
8f7c0a9
bug fixes, a refactor, some other improvements
Toastbrot236 Dec 13, 2024
eae06e0
Refactor some code for more reusability and to replace a hack for lbp…
Toastbrot236 Dec 14, 2024
5a0d739
dont fail to send multiple levels to game completely if atleast one l…
Toastbrot236 Dec 14, 2024
2a2e1f9
Let lbp3 playlists immediately update ingame, remove some no longer n…
Toastbrot236 Dec 14, 2024
36e05d0
nvm, let people reset their playlist name and description in lbp3
Toastbrot236 Dec 14, 2024
d50ce49
Remove some debugging, fixup my code style, slightly easier differenc…
Toastbrot236 Dec 14, 2024
bd67093
Differentiate further between lbp1 and lbp3 specific playlist classes…
Toastbrot236 Dec 14, 2024
b3adc15
Implement custom order for levels in playlists, add some comments
Toastbrot236 Dec 15, 2024
8c85c91
Reword a comment
Toastbrot236 Dec 15, 2024
061a058
Correct another one of my comments, use dedicated method for getting …
Toastbrot236 Dec 15, 2024
966641d
More accurate playlist timestamps (set before write), move foreach in…
Toastbrot236 Dec 16, 2024
6c0c4ab
fore matting, add some comments
Toastbrot236 Dec 16, 2024
ef6f5f9
Reword more comments, always sort levels in playlists, more fore matting
Toastbrot236 Jan 10, 2025
a1328af
Fix some ways of creating recursive playlists not being caught, fix o…
Toastbrot236 Jan 11, 2025
dbdc2a9
Fix that test, sort playlists and playlist levels
Toastbrot236 Jan 11, 2025
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
150 changes: 126 additions & 24 deletions Refresh.GameServer/Database/GameDatabaseContext.Playlists.cs
Original file line number Diff line number Diff line change
@@ -1,38 +1,43 @@
using Refresh.GameServer.Authentication;
using Refresh.GameServer.Extensions;
using Refresh.GameServer.Types;
using Refresh.GameServer.Types.Levels;
using Refresh.GameServer.Types.Playlists;
using Refresh.GameServer.Types.Relations;
using Refresh.GameServer.Types.UserData;

namespace Refresh.GameServer.Database;

public partial class GameDatabaseContext // Playlists
{
public GamePlaylist CreatePlaylist(GameUser user, SerializedPlaylist createInfo, bool rootPlaylist)
public GamePlaylist CreatePlaylist(GameUser user, SerializedLbp1Playlist createInfo, bool rootPlaylist)
{
GamePlaylist playlist = GamePlaylist.ToGamePlaylist(createInfo, user, rootPlaylist);
this.CreatePlaylist(playlist);
return playlist;
}

public GamePlaylist CreatePlaylist(GameUser user, SerializedLbp3Playlist createInfo, bool rootPlaylist)
{
GamePlaylist playlist = GamePlaylist.ToGamePlaylist(createInfo, user, rootPlaylist);
this.CreatePlaylist(playlist);
return playlist;
}

public void CreatePlaylist(GamePlaylist createInfo)
{
GamePlaylist playlist = new()
{
Publisher = user,
Name = createInfo.Name,
Description = createInfo.Description,
IconHash = createInfo.Icon,
LocationX = createInfo.Location.X,
LocationY = createInfo.Location.Y,
IsRoot = rootPlaylist,
};

this.Write(() =>
{
this.AddSequentialObject(playlist);
this.AddSequentialObject(createInfo);
createInfo.CreationDate = this._time.Now;
createInfo.LastUpdateDate = this._time.Now;
});
Toastbrot236 marked this conversation as resolved.
Show resolved Hide resolved

return playlist;
}

public GamePlaylist? GetPlaylistById(int playlistId)
=> this.GamePlaylists.FirstOrDefault(p => p.PlaylistId == playlistId);

public void UpdatePlaylist(GamePlaylist playlist, SerializedPlaylist updateInfo)
public void UpdatePlaylist(GamePlaylist playlist, SerializedLbp1Playlist updateInfo)
{
this.Write(() =>
{
Expand All @@ -41,6 +46,17 @@ public void UpdatePlaylist(GamePlaylist playlist, SerializedPlaylist updateInfo)
playlist.IconHash = updateInfo.Icon;
playlist.LocationX = updateInfo.Location.X;
playlist.LocationY = updateInfo.Location.Y;
playlist.LastUpdateDate = this._time.Now;
});
}

public void UpdatePlaylist(GamePlaylist playlist, SerializedLbp3Playlist updateInfo)
{
this.Write(() =>
{
if (updateInfo.Name != null) playlist.Name = updateInfo.Name;
if (updateInfo.Description != null) playlist.Description = updateInfo.Description;
playlist.LastUpdateDate = this._time.Now;
});
}

Expand All @@ -51,6 +67,7 @@ public void DeletePlaylist(GamePlaylist playlist)
// Remove all relations relating to this playlist
this.LevelPlaylistRelations.RemoveRange(l => l.Playlist == playlist);
this.SubPlaylistRelations.RemoveRange(l => l.Playlist == playlist || l.SubPlaylist == playlist);
this.FavouritePlaylistRelations.RemoveRange(l => l.Playlist == playlist);

// Remove the playlist object
this.GamePlaylists.Remove(playlist);
Expand Down Expand Up @@ -101,24 +118,54 @@ public void AddLevelToPlaylist(GameLevel level, GamePlaylist parent)
{
Level = level,
Playlist = parent,
// Setting TokenGame to LBP3 to get the true amount of levels in the playlist
Index = this.GetTotalLevelsInPlaylistCount(parent, TokenGame.LittleBigPlanet3),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably worth adding a game-agnostic function to get this count, in-case we ever want to expand the playlist system to LBP Vita
I have ideas of using slot overrides to achieve this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

game-agnostic as in not game-dependent, one that doesnt take a TokenGame?

});
});
}

public void RemoveLevelFromPlaylist(GameLevel level, GamePlaylist parent)
{
LevelPlaylistRelation? relation =
this.LevelPlaylistRelations.FirstOrDefault(r => r.Level == level && r.Playlist == parent);

if (relation == null)
return;

// decrease index of every playlist level after this one by 1
this.DecreasePlaylistLevelIndicesAfterIndex(parent, relation.Index);

this.Write(() =>
{
LevelPlaylistRelation? relation =
this.LevelPlaylistRelations.FirstOrDefault(r => r.Level == level && r.Playlist == parent);

if (relation == null)
return;

this.LevelPlaylistRelations.Remove(relation);
});
}

private void DecreasePlaylistLevelIndicesAfterIndex(GamePlaylist playlist, int index)
{
IEnumerable<LevelPlaylistRelation> relations = this.LevelPlaylistRelations
.Where(r => r.Playlist == playlist && r.Index >= index)
.AsEnumerable();


foreach(LevelPlaylistRelation relation in relations)
{
this.Write(() => {
relation.Index--;
});
}
Toastbrot236 marked this conversation as resolved.
Show resolved Hide resolved
}

public void SetPlaylistLevelIndex(GamePlaylist playlist, GameLevel level, int newIndex)
{
LevelPlaylistRelation relation = this.LevelPlaylistRelations
.First(r => r.Playlist == playlist && r.Level == level);

this.Write(() => {
relation.Index = newIndex;
});
}

public IEnumerable<GamePlaylist> GetPlaylistsContainingPlaylist(GamePlaylist playlist)
// TODO: with postgres this can be IQueryable
=> this.SubPlaylistRelations.Where(p => p.SubPlaylist == playlist).AsEnumerable()
Expand All @@ -132,17 +179,35 @@ public IEnumerable<GamePlaylist> GetPlaylistsByAuthorContainingPlaylist(GameUser
.Where(p => p.Publisher.UserId == user.UserId)
.Where(p => !p.IsRoot);

public IEnumerable<GameLevel> GetLevelsInPlaylist(GamePlaylist playlist, TokenGame game) =>
public IEnumerable<GameLevel> GetLevelsInPlaylist(GamePlaylist playlist, TokenGame game)
{
// TODO: When we have postgres, remove the `AsEnumerable` call for performance.
IEnumerable<LevelPlaylistRelation> relations = this.LevelPlaylistRelations
.Where(l => l.Playlist == playlist).AsEnumerable();

// Only sort by order if needed, to improve performance
if (game == TokenGame.LittleBigPlanet3)
relations = relations.OrderBy(r => r.Index);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given theres ideas of using slot overrides to expand this system to other games than LBP1/3, its worth removing this, the amount of time spent ordering is going to be very small (especially after we have Postgres)

Suggested change
// Only sort by order if needed, to improve performance
if (game == TokenGame.LittleBigPlanet3)
relations = relations.OrderBy(r => r.Index);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you are suggesting to remove custom level ordering as a whole and to just not support that feature, because of potential playlist support in other games using level overrides? I am not understanding the link between the two.


return relations.Select(l => l.Level).FilterByGameVersion(game);
}

public int GetTotalLevelsInPlaylistCount(GamePlaylist playlist, TokenGame game) =>
this.LevelPlaylistRelations.Where(l => l.Playlist == playlist).AsEnumerable()
.Select(l => l.Level)
.FilterByGameVersion(game);
.FilterByGameVersion(game)
.Count();

public IEnumerable<GamePlaylist> GetPlaylistsInPlaylist(GamePlaylist playlist)
// TODO: When we have postgres, remove the `AsEnumerable` call for performance.
=> this.SubPlaylistRelations.Where(p => p.Playlist == playlist).AsEnumerable()
.Select(l => l.SubPlaylist);

public IEnumerable<GamePlaylist> GetPlaylistsByAuthor(GameUser author)
// TODO: When we have postgres, remove the `AsEnumerable` call for performance.
=> this.GamePlaylists.Where(p => p.Publisher == author).AsEnumerable()
.Where(p => !p.IsRoot);

public IEnumerable<GamePlaylist> GetPlaylistsByAuthorContainingLevel(GameUser author, GameLevel level)
// TODO: When we have postgres, remove the `AsEnumerable` call for performance.
=> this.LevelPlaylistRelations.Where(p => p.Level == level).AsEnumerable()
Expand All @@ -153,4 +218,41 @@ public IEnumerable<GamePlaylist> GetPlaylistsContainingLevel(GameLevel level)
// TODO: When we have postgres, remove the `AsEnumerable` call for performance.
=> this.LevelPlaylistRelations.Where(p => p.Level == level).AsEnumerable()
.Select(r => this.GamePlaylists.First(p => p.PlaylistId == r.Playlist.PlaylistId));

public bool IsPlaylistFavouritedByUser(GamePlaylist playlist, GameUser user)
=> this.FavouritePlaylistRelations.FirstOrDefault(r => r.Playlist == playlist && r.User == user) != null;

public IEnumerable<GamePlaylist> GetPlaylistsFavouritedByUser(GameUser user) => this.FavouritePlaylistRelations
.Where(r => r.User == user).AsEnumerable()
.Select(r => r.Playlist);
Toastbrot236 marked this conversation as resolved.
Show resolved Hide resolved

public int GetFavouriteCountForPlaylist(GamePlaylist playlist)
=> this.FavouritePlaylistRelations
.Count(r => r.Playlist == playlist);

public bool FavouritePlaylist(GamePlaylist playlist, GameUser user)
{
if (this.IsPlaylistFavouritedByUser(playlist, user)) return false;

FavouritePlaylistRelation relation = new()
{
Playlist = playlist,
User = user,
};
this.Write(() => this.FavouritePlaylistRelations.Add(relation));

return true;
}

public bool UnfavouritePlaylist(GamePlaylist playlist, GameUser user)
{
FavouritePlaylistRelation? relation = this.FavouritePlaylistRelations
.FirstOrDefault(r => r.Playlist == playlist && r.User == user);

if (relation == null) return false;

this.Write(() => this.FavouritePlaylistRelations.Remove(relation));

return true;
}
}
1 change: 1 addition & 0 deletions Refresh.GameServer/Database/GameDatabaseContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public partial class GameDatabaseContext : RealmDatabaseContext
private RealmDbSet<GamePlaylist> GamePlaylists => new(this._realm);
private RealmDbSet<LevelPlaylistRelation> LevelPlaylistRelations => new(this._realm);
private RealmDbSet<SubPlaylistRelation> SubPlaylistRelations => new(this._realm);
private RealmDbSet<FavouritePlaylistRelation> FavouritePlaylistRelations => new(this._realm);
private RealmDbSet<GameUserVerifiedIpRelation> GameUserVerifiedIpRelations => new(this._realm);

internal GameDatabaseContext(IDateTimeProvider time)
Expand Down
20 changes: 18 additions & 2 deletions Refresh.GameServer/Database/GameDatabaseProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ protected GameDatabaseProvider(IDateTimeProvider time)
this._time = time;
}

protected override ulong SchemaVersion => 161;
protected override ulong SchemaVersion => 163;

protected override string Filename => "refreshGameServer.realm";

Expand Down Expand Up @@ -87,6 +87,7 @@ protected GameDatabaseProvider(IDateTimeProvider time)
typeof(GamePlaylist),
typeof(LevelPlaylistRelation),
typeof(SubPlaylistRelation),
typeof(FavouritePlaylistRelation)
];

public override void Warmup()
Expand Down Expand Up @@ -686,7 +687,7 @@ protected override void Migrate(Migration migration, ulong oldVersion)
// We weren't deleting level playlist relations when a level was deleted. Version 160 fixes this.
if (oldVersion < 160)
migration.NewRealm.RemoveRange(migration.NewRealm.All<LevelPlaylistRelation>().Where(r => r.Level == null));

// IQueryable<dynamic>? oldLevelPlaylistRelations = migration.OldRealm.DynamicApi.All("LevelPlaylistRelation");
// IQueryable<LevelPlaylistRelation>? newLevelPlaylistRelations = migration.NewRealm.All<LevelPlaylistRelation>();
// if (oldVersion < 155)
Expand All @@ -704,5 +705,20 @@ protected override void Migrate(Migration migration, ulong oldVersion)
// dynamic oldSubPlaylistRelation = oldSubPlaylistRelations.ElementAt(i);
// SubPlaylistRelation newSubPlaylistRelation = newSubPlaylistRelations.ElementAt(i);
// }

// Version 163 added indices for LevelPlaylistRelations for custom playlist level order in LBP3
IQueryable<dynamic>? oldLevelPlaylistRelations = migration.OldRealm.DynamicApi.All("LevelPlaylistRelation");
IQueryable<LevelPlaylistRelation>? newLevelPlaylistRelations = migration.NewRealm.All<LevelPlaylistRelation>();
if (oldVersion < 163)
for (int i = 0; i < newLevelPlaylistRelations.Count(); i++)
{
dynamic oldLevelPlaylistRelation = oldLevelPlaylistRelations.ElementAt(i);
LevelPlaylistRelation newLevelPlaylistRelation = newLevelPlaylistRelations.ElementAt(i);

if (oldVersion < 163)
{
newLevelPlaylistRelation.Index = 0;
}
}
}
}
2 changes: 1 addition & 1 deletion Refresh.GameServer/Endpoints/Game/Levels/LevelEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ public class LevelEndpoints : EndpointGroup

foreach (string levelIdStr in levelIds)
{
if (!int.TryParse(levelIdStr, out int levelId)) return null;
if (!int.TryParse(levelIdStr.StartsWith('d') ? levelIdStr[1..] : levelIdStr, out int levelId)) continue;
GameLevel? level = database.GetLevelById(levelId);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you document here what this extra d check is for?


if (level == null) continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@

namespace Refresh.GameServer.Endpoints.Game.Playlists;

public class PlaylistEndpoints : EndpointGroup
public class PlaylistLbp1Endpoints : EndpointGroup
{
// Creates a playlist, with an optional parent ID
[GameEndpoint("createPlaylist", HttpMethods.Post, ContentType.Xml)]
[RequireEmailVerified]
public Response CreatePlaylist(RequestContext context, DataContext dataContext, SerializedPlaylist body)
public Response CreatePlaylist(RequestContext context, DataContext dataContext, SerializedLbp1Playlist body)
{
GameUser user = dataContext.User!;

Expand All @@ -43,6 +43,8 @@ public Response CreatePlaylist(RequestContext context, DataContext dataContext,
return BadRequest;
}



Toastbrot236 marked this conversation as resolved.
Show resolved Hide resolved
// Create the playlist, marking it as the root playlist if the user does not have one set already
GamePlaylist playlist = dataContext.Database.CreatePlaylist(user, body, user.RootPlaylist == null);

Expand All @@ -55,7 +57,7 @@ public Response CreatePlaylist(RequestContext context, DataContext dataContext,
dataContext.Database.SetUserRootPlaylist(user, playlist);

// Create the new playlist, returning the data
return new Response(SerializedPlaylist.FromOld(playlist, dataContext), ContentType.Xml);
return new Response(SerializedLbp1Playlist.FromOld(playlist, dataContext), ContentType.Xml);
}

// Gets the slots contained within a playlist
Expand Down Expand Up @@ -147,7 +149,7 @@ public Response CreatePlaylist(RequestContext context, DataContext dataContext,

[GameEndpoint("setPlaylistMetaData/{id}", HttpMethods.Post, ContentType.Xml)]
[RequireEmailVerified]
public Response UpdatePlaylistMetadata(RequestContext context, GameDatabaseContext database, GameUser user, int id, SerializedPlaylist body)
public Response UpdatePlaylistMetadata(RequestContext context, GameDatabaseContext database, GameUser user, int id, SerializedLbp1Playlist body)
{
GamePlaylist? playlist = database.GetPlaylistById(id);
if (playlist == null)
Expand Down
Loading
Loading