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

Main #103

Merged
merged 8 commits into from
Mar 12, 2024
Merged

Main #103

Show file tree
Hide file tree
Changes from 7 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
26 changes: 26 additions & 0 deletions deployment/game_database/vector_index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Create the following atlas vector search index:

// ==========================
// named: vector_index
// collection: recordings
// ==========================
{
"fields": [
{
"path": "Player.Nickname",
"type": "filter"
},
{
"numDimensions": 589,
"path": "speed_vector",
"similarity": "euclidean",
"type": "vector"
},
{
"numDimensions": 588,
"path": "accel_vector",
"similarity": "euclidean",
"type": "vector"
}
]
}
122 changes: 122 additions & 0 deletions rest_service/Controllers/RecordingsController.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using Microsoft.AspNetCore.Mvc;
using MongoDB.Driver;
using RestService.Dtos.RequestObjects;
using RestService.Dtos.ResponseObjects;
using RestService.Entities;
using RestService.Exceptions;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;

namespace RestService.Controllers;

Expand Down Expand Up @@ -38,6 +41,7 @@ public async Task<IActionResult> PostRecording([FromBody] RecordingRequest recor

var newRecording = new Recording()
{
Id = ObjectId.GenerateNewId(),
SessionStatisticsPlain = recordingRequest.SessionStatisticsPlain,
DateTime = DateTime.UtcNow,
Player = new RecordingPlayer { Name = recordingRequest.PlayerName },
Expand All @@ -53,6 +57,15 @@ public async Task<IActionResult> PostRecording([FromBody] RecordingRequest recor
}).ToList()
};

// Calculate vectors
try
{
newRecording.SpeedVector = CalculateSpeedVector(newRecording.Snapshots);
newRecording.AccelVector = CalculateAcceleration(newRecording.SpeedVector);
} catch (Exception) {
// Favor persisting Recording over setting vectors
}

try
{
await AddLocation(newRecording);
Expand Down Expand Up @@ -129,4 +142,113 @@ private async Task AddPlayer(Recording recording)
throw new MultiplePlayersFoundException();
}
}

private static double[] CalculateSpeedVector(List<Snapshot> snapshots)
{
long vectorSize = snapshots.Count - 1;
if (vectorSize != 589) // 590 represents movements in 60s
return Array.Empty<double>();

double[] speed = new double[vectorSize];
for (int i = 0; i < vectorSize; i++)
{
double speedX = snapshots[i + 1].Position.X - snapshots[i].Position.X;
//double Y = snapshots[i + 1].Position.X - snapshots[i].Position.X;
double speedY = snapshots[i + 1].Position.Z - snapshots[i].Position.Z;
speed[i] = Math.Sqrt(Math.Pow(speedX, 2) + Math.Pow(speedY, 2));
}

return speed;
}

private static double[] CalculateAcceleration(double[] speedVector)
{
long vectorSize = speedVector.Length - 1;
if (speedVector.Length != 589) // speed vector should be 1 less 60s run
return Array.Empty<double>();

double dt = 1; // Assuming a constant time step of 1 unit.
double[] accelVector = new double[vectorSize];

for (int i = 0; i < speedVector.Length - 1; i++)
accelVector[i] = (speedVector[i + 1] - speedVector[i]) / dt;

return accelVector;
}

[HttpGet("similarBySpeed", Name = "GetSimilarBySpeed")]
public async Task<List<SimilarRecordingResponse>> SimilarBySpeed([FromQuery] PlayerRequest playerRequest)
{
// Get the highest scoring run for this player
Recording topRecording = _recordingsCollection
.Find(r => r.Player.Name.Equals(playerRequest.Name))
.SortByDescending(r => r.SessionStatisticsPlain.Score)
.Limit(1).ToList().First();

// Now get similar recordings
List<Recording> similarRecordings = _recordingsCollection.Aggregate()
.VectorSearch(
r => r.SpeedVector,
topRecording.SpeedVector,
3,
new VectorSearchOptions<Recording>()
{
IndexName = "vector_index",
NumberOfCandidates = 1000,
Filter = Builders<Recording>.Filter
.Where(r => !r.Player.Name.Equals(playerRequest.Name))
})
.ToList();

// Return this player's top recording + top similar
List<SimilarRecordingResponse> response = new()
{
new SimilarRecordingResponse(topRecording)
};
response.AddRange(
similarRecordings
.Select(r => new SimilarRecordingResponse(r))
.ToList());

return response;
}


[HttpGet("similarByAcceleration", Name = "GetSimilarByAcceleration")]
public async Task<List<SimilarRecordingResponse>> SimilarByAcceleration([FromQuery] PlayerRequest playerRequest)
{
// Get the highest scoring run for this player
Recording topRecording = _recordingsCollection
.Find(r => r.Player.Name.Equals(playerRequest.Name))
.SortByDescending(r => r.SessionStatisticsPlain.Score)
.Limit(1).ToList().First();

// Now get similar recordings
List<Recording> similarRecordings = _recordingsCollection.Aggregate()
.VectorSearch(
r => r.AccelVector,
topRecording.AccelVector,
3,
new VectorSearchOptions<Recording>()
{
IndexName = "vector_index",
NumberOfCandidates = 1000,
Filter = Builders<Recording>.Filter
.Where(r => !r.Player.Name.Equals(playerRequest.Name))
})
.ToList();

// Return this player's top recording + top similar
List<SimilarRecordingResponse> response = new()
{
new SimilarRecordingResponse(topRecording)
};
response.AddRange(
similarRecordings
.Select(r => new SimilarRecordingResponse(r))
.ToList());

return response;
}

}
5 changes: 1 addition & 4 deletions rest_service/Dtos/RequestObjects/RecordingRequest.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using RestService.Entities;
using RestService.Entities;

namespace RestService.Dtos.RequestObjects;

[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
[SuppressMessage("ReSharper", "CollectionNeverUpdated.Global")]
public class RecordingRequest
{
public SessionStatisticsPlain? SessionStatisticsPlain { get; set; }
Expand Down
3 changes: 0 additions & 3 deletions rest_service/Dtos/RequestObjects/SnapshotRequest.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using RestService.Entities;

namespace RestService.Dtos.RequestObjects;

[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
public class SnapshotRequest
{
public Position? Position { get; set; }
Expand Down
1 change: 0 additions & 1 deletion rest_service/Dtos/ResponseObjects/ConfigResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

namespace RestService.Dtos.ResponseObjects;

[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
public class ConfigResponse
{
public float RoundDuration { get; set; }
Expand Down
12 changes: 0 additions & 12 deletions rest_service/Dtos/ResponseObjects/PlayerResponse.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
using System.Diagnostics.CodeAnalysis;
using MongoDB.Bson.Serialization.Attributes;
using Newtonsoft.Json;
using RestService.Entities;

namespace RestService.Dtos.ResponseObjects;

[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
public class PlayerResponse
{
[JsonProperty("_id")] public string? Id { get; set; }
Expand All @@ -22,13 +19,4 @@ public PlayerResponse(Player player)
Team = player.Team;
Location = player.Location;
}

/*
public PlayerResponse(PlayerUnique player)
{
Id = player.Id.ToString();
Name = player.Name;
Location = player.Location;
}
*/
}
22 changes: 22 additions & 0 deletions rest_service/Dtos/ResponseObjects/SimilarRecordingResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Newtonsoft.Json;
using RestService.Entities;

namespace RestService.Dtos.ResponseObjects;

public class SimilarRecordingResponse
{
[JsonProperty("_id")]
public string? Id { get; set; }
[JsonProperty("sessionStatisticsPlain")]
public SessionStatisticsPlain? SessionStatisticsPlain { get; set; }
[JsonProperty("name")]
public string? Name { get; set; }

public SimilarRecordingResponse(Recording recording)
{
Id = recording.Id.ToString();
SessionStatisticsPlain = recording.SessionStatisticsPlain;
Name = recording.Player.Name;
}
}

4 changes: 1 addition & 3 deletions rest_service/Entities/Config.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
using System.Diagnostics.CodeAnalysis;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;

namespace RestService.Entities;

[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[BsonIgnoreExtraElements]
public class Config
{
[BsonElement("_id")] public ObjectId? Id { get; set; }
Expand Down
4 changes: 1 addition & 3 deletions rest_service/Entities/Event.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
using System.Diagnostics.CodeAnalysis;
using MongoDB.Bson.Serialization.Attributes;

namespace RestService.Entities;

[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
[SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")]
[BsonIgnoreExtraElements]
public class Event
{
[BsonElement("_id")] public string? Id { get; set; }
Expand Down
7 changes: 1 addition & 6 deletions rest_service/Entities/Player.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
using System.Diagnostics.CodeAnalysis;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Newtonsoft.Json;
using RestService.Dtos.RequestObjects;

namespace RestService.Entities;

[SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")]
[BsonIgnoreExtraElements]
public class Player
{
[BsonElement("_id")] public ObjectId? Id { get; set; }
[BsonElement("Nickname")] public string? Name { get; set; }
[BsonElement("TeamName")] public string? Team { get; set; }
[BsonElement("Email")] public string? Email { get; set; }

[JsonProperty("location")]
[BsonElement("location")]
public string? Location { get; set; }
}
18 changes: 7 additions & 11 deletions rest_service/Entities/PlayerUnique.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Newtonsoft.Json;

namespace RestService.Entities;

namespace RestService.Entities
[BsonIgnoreExtraElements]
public class PlayerUnique
{
public class PlayerUnique
{
[BsonId]
[JsonProperty("Name")]
public string Name { get; set; }
[BsonId]
public string Name { get; set; }

[JsonProperty("location")]
[BsonElement("location")]
public string? Location { get; set; }
}
[BsonElement("location")]
public string? Location { get; set; }
}
10 changes: 5 additions & 5 deletions rest_service/Entities/Position.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using System.Diagnostics.CodeAnalysis;
using MongoDB.Bson.Serialization.Attributes;

namespace RestService.Entities;

[SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")]
[BsonIgnoreExtraElements]
public class Position
{
public float X { get; set; }
public float Y { get; set; }
public float Z { get; set; }
public double X { get; set; }
public double Y { get; set; }
public double Z { get; set; }
}
12 changes: 9 additions & 3 deletions rest_service/Entities/Recording.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
using System.Diagnostics.CodeAnalysis;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;

namespace RestService.Entities;

[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
[BsonIgnoreExtraElements]
public class Recording
{
[BsonElement("location")] public string? Location { get; set; }
[BsonElement("_id")] public ObjectId? Id { get; set; }
[BsonElement("location")]
public string? Location { get; set; }
public SessionStatisticsPlain? SessionStatisticsPlain { get; set; }
public DateTime? DateTime { get; set; }
public RecordingPlayer Player { get; set; }
public List<Snapshot>? Snapshots { get; set; }
public RecordingEvent Event { get; set; }
[BsonElement("speed_vector")]
public double[]? SpeedVector { get; set; }
[BsonElement("accel_vector")]
public double[]? AccelVector { get; set; }
}
Loading