Speeding up visible location call
All checks were successful
Deploy Promiscuity Auth API / deploy (push) Successful in 46s
Deploy Promiscuity Character API / deploy (push) Successful in 57s
Deploy Promiscuity Inventory API / deploy (push) Successful in 59s
Deploy Promiscuity Locations API / deploy (push) Successful in 58s
k8s smoke test / test (push) Successful in 8s
All checks were successful
Deploy Promiscuity Auth API / deploy (push) Successful in 46s
Deploy Promiscuity Character API / deploy (push) Successful in 57s
Deploy Promiscuity Inventory API / deploy (push) Successful in 59s
Deploy Promiscuity Locations API / deploy (push) Successful in 58s
k8s smoke test / test (push) Successful in 8s
This commit is contained in:
parent
8ce6a05710
commit
038981d7b1
@ -15,9 +15,10 @@ const INVENTORY_API_URL := "https://pinv.ranaze.com/api/inventory"
|
|||||||
@export var tile_label_height := 0.01
|
@export var tile_label_height := 0.01
|
||||||
@export var tile_label_color: Color = Color(1, 1, 1, 1)
|
@export var tile_label_color: Color = Color(1, 1, 1, 1)
|
||||||
|
|
||||||
@onready var _block: MeshInstance3D = $TerrainBlock
|
@onready var _block: MeshInstance3D = $TerrainBlock
|
||||||
@onready var _player: RigidBody3D = $Player
|
@onready var _player: RigidBody3D = $Player
|
||||||
@onready var _camera: Camera3D = $Player/Camera3D
|
@onready var _camera: Camera3D = $Player/Camera3D
|
||||||
|
@onready var _player_visual: Node3D = $Player/TestCharAnimated
|
||||||
|
|
||||||
var _center_coord := Vector2i.ZERO
|
var _center_coord := Vector2i.ZERO
|
||||||
var _tiles_root: Node3D
|
var _tiles_root: Node3D
|
||||||
@ -37,10 +38,10 @@ var _queued_locations_refresh := false
|
|||||||
var _interact_in_flight := false
|
var _interact_in_flight := false
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
_tiles_root = Node3D.new()
|
_tiles_root = Node3D.new()
|
||||||
_tiles_root.name = "GeneratedTiles"
|
_tiles_root.name = "GeneratedTiles"
|
||||||
add_child(_tiles_root)
|
add_child(_tiles_root)
|
||||||
|
|
||||||
if _camera:
|
if _camera:
|
||||||
_camera_start_offset = _camera.position
|
_camera_start_offset = _camera.position
|
||||||
@ -50,15 +51,17 @@ func _ready() -> void:
|
|||||||
_tracked_node = _player
|
_tracked_node = _player
|
||||||
|
|
||||||
var start_coord := SelectedCharacter.get_coord()
|
var start_coord := SelectedCharacter.get_coord()
|
||||||
_center_coord = Vector2i(roundi(start_coord.x), roundi(start_coord.y))
|
_center_coord = Vector2i(roundi(start_coord.x), roundi(start_coord.y))
|
||||||
_persisted_coord = _center_coord
|
_persisted_coord = _center_coord
|
||||||
_character_id = String(SelectedCharacter.character.get("id", SelectedCharacter.character.get("Id", ""))).strip_edges()
|
_character_id = String(SelectedCharacter.character.get("id", SelectedCharacter.character.get("Id", ""))).strip_edges()
|
||||||
|
|
||||||
_block.visible = false
|
_block.visible = false
|
||||||
await _load_existing_locations()
|
_deactivate_player_for_load()
|
||||||
_ensure_selected_location_exists(_center_coord)
|
await _load_existing_locations()
|
||||||
_rebuild_tiles(_center_coord)
|
_ensure_selected_location_exists(_center_coord)
|
||||||
_move_player_to_coord(_center_coord)
|
_rebuild_tiles(_center_coord)
|
||||||
|
_move_player_to_coord(_center_coord)
|
||||||
|
_activate_player_after_load()
|
||||||
|
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
func _process(_delta: float) -> void:
|
||||||
@ -95,12 +98,34 @@ func _coord_to_world(coord: Vector2i) -> Vector3:
|
|||||||
return Vector3(coord.x * tile_size, block_height * 0.5, coord.y * tile_size)
|
return Vector3(coord.x * tile_size, block_height * 0.5, coord.y * tile_size)
|
||||||
|
|
||||||
|
|
||||||
func _move_player_to_coord(coord: Vector2i) -> void:
|
func _move_player_to_coord(coord: Vector2i) -> void:
|
||||||
if _player == null:
|
if _player == null:
|
||||||
return
|
return
|
||||||
_player.global_position = Vector3(coord.x * tile_size, player_spawn_height, coord.y * tile_size)
|
_player.global_position = Vector3(coord.x * tile_size, player_spawn_height, coord.y * tile_size)
|
||||||
_player.linear_velocity = Vector3.ZERO
|
_player.linear_velocity = Vector3.ZERO
|
||||||
_player.angular_velocity = Vector3.ZERO
|
_player.angular_velocity = Vector3.ZERO
|
||||||
|
|
||||||
|
|
||||||
|
func _deactivate_player_for_load() -> void:
|
||||||
|
if _player == null:
|
||||||
|
return
|
||||||
|
_player.freeze = true
|
||||||
|
_player.sleeping = true
|
||||||
|
_player.linear_velocity = Vector3.ZERO
|
||||||
|
_player.angular_velocity = Vector3.ZERO
|
||||||
|
if _player_visual:
|
||||||
|
_player_visual.visible = false
|
||||||
|
|
||||||
|
|
||||||
|
func _activate_player_after_load() -> void:
|
||||||
|
if _player == null:
|
||||||
|
return
|
||||||
|
_player.linear_velocity = Vector3.ZERO
|
||||||
|
_player.angular_velocity = Vector3.ZERO
|
||||||
|
_player.sleeping = false
|
||||||
|
_player.freeze = false
|
||||||
|
if _player_visual:
|
||||||
|
_player_visual.visible = true
|
||||||
|
|
||||||
|
|
||||||
func _rebuild_tiles(center: Vector2i) -> void:
|
func _rebuild_tiles(center: Vector2i) -> void:
|
||||||
@ -423,7 +448,7 @@ func _load_existing_locations() -> void:
|
|||||||
"name": location_name,
|
"name": location_name,
|
||||||
"biomeKey": String(location.get("biomeKey", "plains")).strip_edges(),
|
"biomeKey": String(location.get("biomeKey", "plains")).strip_edges(),
|
||||||
"locationObject": _parse_location_object(location.get("locationObject", {})),
|
"locationObject": _parse_location_object(location.get("locationObject", {})),
|
||||||
"floorItems": []
|
"floorItems": _parse_floor_inventory_items(location.get("floorItems", []))
|
||||||
}
|
}
|
||||||
loaded_count += 1
|
loaded_count += 1
|
||||||
|
|
||||||
@ -431,11 +456,9 @@ func _load_existing_locations() -> void:
|
|||||||
if loaded_count == 0:
|
if loaded_count == 0:
|
||||||
push_warning("Visible locations request succeeded but returned 0 locations for character %s." % _character_id)
|
push_warning("Visible locations request succeeded but returned 0 locations for character %s." % _character_id)
|
||||||
|
|
||||||
await _load_visible_location_inventories()
|
_locations_loaded = true
|
||||||
|
_locations_refresh_in_flight = false
|
||||||
_locations_loaded = true
|
_rebuild_tiles(_center_coord)
|
||||||
_locations_refresh_in_flight = false
|
|
||||||
_rebuild_tiles(_center_coord)
|
|
||||||
if _queued_locations_refresh:
|
if _queued_locations_refresh:
|
||||||
_queued_locations_refresh = false
|
_queued_locations_refresh = false
|
||||||
_queue_locations_refresh()
|
_queue_locations_refresh()
|
||||||
@ -633,6 +656,7 @@ func _parse_floor_inventory_items(value: Variant) -> Array:
|
|||||||
continue
|
continue
|
||||||
var item := entry as Dictionary
|
var item := entry as Dictionary
|
||||||
items.append({
|
items.append({
|
||||||
|
"itemId": String(item.get("itemId", item.get("id", ""))).strip_edges(),
|
||||||
"itemKey": String(item.get("itemKey", "")).strip_edges(),
|
"itemKey": String(item.get("itemKey", "")).strip_edges(),
|
||||||
"quantity": int(item.get("quantity", 0)),
|
"quantity": int(item.get("quantity", 0)),
|
||||||
"slot": item.get("slot", null)
|
"slot": item.get("slot", null)
|
||||||
@ -640,19 +664,6 @@ func _parse_floor_inventory_items(value: Variant) -> Array:
|
|||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
|
||||||
func _load_visible_location_inventories() -> void:
|
|
||||||
for coord_variant in _known_locations.keys():
|
|
||||||
var coord: Vector2i = coord_variant
|
|
||||||
var location_data: Dictionary = _known_locations[coord]
|
|
||||||
var location_id := String(location_data.get("id", "")).strip_edges()
|
|
||||||
if location_id.is_empty():
|
|
||||||
continue
|
|
||||||
var floor_items := await _fetch_location_inventory(location_id)
|
|
||||||
location_data["floorItems"] = floor_items
|
|
||||||
_known_locations[coord] = location_data
|
|
||||||
|
|
||||||
|
|
||||||
func _refresh_location_inventory(location_id: String) -> void:
|
func _refresh_location_inventory(location_id: String) -> void:
|
||||||
if location_id.is_empty():
|
if location_id.is_empty():
|
||||||
return
|
return
|
||||||
|
|||||||
@ -0,0 +1,12 @@
|
|||||||
|
namespace CharacterApi.Models;
|
||||||
|
|
||||||
|
public class FloorInventoryItemResponse
|
||||||
|
{
|
||||||
|
public string ItemId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string ItemKey { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public int Quantity { get; set; }
|
||||||
|
|
||||||
|
public int? Slot { get; set; }
|
||||||
|
}
|
||||||
@ -21,4 +21,7 @@ public class VisibleLocation
|
|||||||
|
|
||||||
[BsonElement("locationObject")]
|
[BsonElement("locationObject")]
|
||||||
public VisibleLocationObject? LocationObject { get; set; }
|
public VisibleLocationObject? LocationObject { get; set; }
|
||||||
|
|
||||||
|
[BsonElement("floorItems")]
|
||||||
|
public List<FloorInventoryItemResponse> FloorItems { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,37 @@ public class InventoryController : ControllerBase
|
|||||||
_inventory = inventory;
|
_inventory = inventory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("internal/by-owner/{ownerType}")]
|
||||||
|
public async Task<IActionResult> GetByOwnersInternal(string ownerType, [FromBody] InternalOwnerInventoryBatchRequest req)
|
||||||
|
{
|
||||||
|
var configuredKey = (HttpContext.RequestServices.GetRequiredService<IConfiguration>()["InternalApi:Key"]
|
||||||
|
?? HttpContext.RequestServices.GetRequiredService<IConfiguration>()["Jwt:Key"]
|
||||||
|
?? string.Empty).Trim();
|
||||||
|
var requestKey = (Request.Headers["X-Internal-Api-Key"].FirstOrDefault() ?? string.Empty).Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(configuredKey) || !string.Equals(configuredKey, requestKey, StringComparison.Ordinal))
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
var normalizedOwnerType = ownerType.Trim().ToLowerInvariant();
|
||||||
|
if (normalizedOwnerType is not ("character" or "location"))
|
||||||
|
return BadRequest("Unsupported ownerType");
|
||||||
|
|
||||||
|
var groupedItems = await _inventory.GetByOwnersAsync(normalizedOwnerType, req.OwnerIds);
|
||||||
|
var ownerIds = req.OwnerIds
|
||||||
|
.Where(id => !string.IsNullOrWhiteSpace(id))
|
||||||
|
.Select(id => id.Trim())
|
||||||
|
.Distinct(StringComparer.Ordinal)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var response = ownerIds.Select(ownerId => new OwnerInventorySummaryResponse
|
||||||
|
{
|
||||||
|
OwnerType = normalizedOwnerType,
|
||||||
|
OwnerId = ownerId,
|
||||||
|
Items = groupedItems.GetValueOrDefault(ownerId, []).Select(InventoryItemResponse.FromModel).ToList()
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
return Ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet("item-definitions")]
|
[HttpGet("item-definitions")]
|
||||||
[Authorize(Roles = "USER,SUPER")]
|
[Authorize(Roles = "USER,SUPER")]
|
||||||
public async Task<IActionResult> ListItemDefinitions()
|
public async Task<IActionResult> ListItemDefinitions()
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
namespace InventoryApi.Models;
|
||||||
|
|
||||||
|
public class InternalOwnerInventoryBatchRequest
|
||||||
|
{
|
||||||
|
public List<string> OwnerIds { get; set; } = [];
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
namespace InventoryApi.Models;
|
||||||
|
|
||||||
|
public class OwnerInventorySummaryResponse
|
||||||
|
{
|
||||||
|
public string OwnerType { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string OwnerId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public List<InventoryItemResponse> Items { get; set; } = [];
|
||||||
|
}
|
||||||
@ -88,6 +88,32 @@ public class InventoryStore
|
|||||||
.ThenBy(i => i.ItemKey)
|
.ThenBy(i => i.ItemKey)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
|
public async Task<Dictionary<string, List<InventoryItem>>> GetByOwnersAsync(string ownerType, IEnumerable<string> ownerIds)
|
||||||
|
{
|
||||||
|
var normalizedOwnerType = NormalizeOwnerType(ownerType);
|
||||||
|
if (normalizedOwnerType is null)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
var ids = ownerIds
|
||||||
|
.Where(id => !string.IsNullOrWhiteSpace(id))
|
||||||
|
.Select(id => id.Trim())
|
||||||
|
.Distinct(StringComparer.Ordinal)
|
||||||
|
.ToList();
|
||||||
|
if (ids.Count == 0)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
var items = await _items.Find(i => i.OwnerType == normalizedOwnerType && ids.Contains(i.OwnerId))
|
||||||
|
.SortBy(i => i.OwnerId)
|
||||||
|
.ThenBy(i => i.EquippedSlot)
|
||||||
|
.ThenBy(i => i.Slot)
|
||||||
|
.ThenBy(i => i.ItemKey)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return items
|
||||||
|
.GroupBy(item => item.OwnerId, StringComparer.Ordinal)
|
||||||
|
.ToDictionary(group => group.Key, group => group.ToList(), StringComparer.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
public Task<List<ItemDefinition>> ListItemDefinitionsAsync() =>
|
public Task<List<ItemDefinition>> ListItemDefinitionsAsync() =>
|
||||||
_definitions.Find(Builders<ItemDefinition>.Filter.Empty).SortBy(d => d.ItemKey).ToListAsync();
|
_definitions.Find(Builders<ItemDefinition>.Filter.Empty).SortBy(d => d.ItemKey).ToListAsync();
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"InternalApi": {
|
||||||
|
"Key": "SuperUltraSecureJwtKeyWithAtLeast32Chars!!"
|
||||||
|
},
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"Kestrel": { "Endpoints": { "Http": { "Url": "http://0.0.0.0:5003" } } },
|
"Kestrel": { "Endpoints": { "Http": { "Url": "http://0.0.0.0:5003" } } },
|
||||||
"MongoDB": { "ConnectionString": "mongodb://192.168.86.50:27017", "DatabaseName": "promiscuity" },
|
"MongoDB": { "ConnectionString": "mongodb://192.168.86.50:27017", "DatabaseName": "promiscuity" },
|
||||||
|
"InternalApi": { "Key": "SuperUltraSecureJwtKeyWithAtLeast32Chars!!" },
|
||||||
"Jwt": { "Key": "SuperUltraSecureJwtKeyWithAtLeast32Chars!!", "Issuer": "promiscuity", "Audience": "promiscuity-auth-api" },
|
"Jwt": { "Key": "SuperUltraSecureJwtKeyWithAtLeast32Chars!!", "Issuer": "promiscuity", "Audience": "promiscuity-auth-api" },
|
||||||
"Logging": { "LogLevel": { "Default": "Information" } },
|
"Logging": { "LogLevel": { "Default": "Information" } },
|
||||||
"AllowedHosts": "*"
|
"AllowedHosts": "*"
|
||||||
|
|||||||
@ -91,6 +91,7 @@ public class LocationsController : ControllerBase
|
|||||||
return BadRequest("radius must be non-negative");
|
return BadRequest("radius must be non-negative");
|
||||||
|
|
||||||
var result = await _locations.GetOrCreateVisibleLocationsAsync(req.X, req.Y, req.Radius);
|
var result = await _locations.GetOrCreateVisibleLocationsAsync(req.X, req.Y, req.Radius);
|
||||||
|
await PopulateFloorInventoriesAsync(result);
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,4 +368,54 @@ public class LocationsController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static string NormalizeBiomeKey(string biomeKey) => biomeKey.Trim().ToLowerInvariant();
|
private static string NormalizeBiomeKey(string biomeKey) => biomeKey.Trim().ToLowerInvariant();
|
||||||
|
|
||||||
|
private async Task PopulateFloorInventoriesAsync(VisibleLocationWindowResponse result)
|
||||||
|
{
|
||||||
|
var locationIds = result.Locations
|
||||||
|
.Where(location => !string.IsNullOrWhiteSpace(location.Id))
|
||||||
|
.Select(location => location.Id!)
|
||||||
|
.Distinct(StringComparer.Ordinal)
|
||||||
|
.ToList();
|
||||||
|
if (locationIds.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var inventoryBaseUrl = (_configuration["Services:InventoryApiBaseUrl"] ?? "http://localhost:5003").TrimEnd('/');
|
||||||
|
var internalApiKey = (_configuration["InternalApi:Key"] ?? _configuration["Jwt:Key"] ?? string.Empty).Trim();
|
||||||
|
var body = JsonSerializer.Serialize(new { ownerIds = locationIds });
|
||||||
|
|
||||||
|
var client = _httpClientFactory.CreateClient();
|
||||||
|
using var request = new HttpRequestMessage(HttpMethod.Post, $"{inventoryBaseUrl}/api/inventory/internal/by-owner/location");
|
||||||
|
request.Content = new StringContent(body, Encoding.UTF8, "application/json");
|
||||||
|
request.Headers.Add("X-Internal-Api-Key", internalApiKey);
|
||||||
|
|
||||||
|
using var response = await client.SendAsync(request);
|
||||||
|
var responseBody = await response.Content.ReadAsStringAsync();
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
"Failed to load batched floor inventories from InventoryApi. Status={StatusCode} Body={Body}",
|
||||||
|
(int)response.StatusCode,
|
||||||
|
responseBody);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parsed = JsonSerializer.Deserialize<List<OwnerInventorySummaryEnvelope>>(
|
||||||
|
responseBody,
|
||||||
|
new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? [];
|
||||||
|
var byOwnerId = parsed.ToDictionary(entry => entry.OwnerId, entry => entry.Items, StringComparer.Ordinal);
|
||||||
|
|
||||||
|
foreach (var location in result.Locations)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(location.Id))
|
||||||
|
continue;
|
||||||
|
location.FloorItems = byOwnerId.GetValueOrDefault(location.Id!, []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class OwnerInventorySummaryEnvelope
|
||||||
|
{
|
||||||
|
public string OwnerId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public List<FloorInventoryItemResponse> Items { get; set; } = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,12 @@
|
|||||||
|
namespace LocationsApi.Models;
|
||||||
|
|
||||||
|
public class FloorInventoryItemResponse
|
||||||
|
{
|
||||||
|
public string ItemId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string ItemKey { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public int Quantity { get; set; }
|
||||||
|
|
||||||
|
public int? Slot { get; set; }
|
||||||
|
}
|
||||||
@ -11,4 +11,6 @@ public class VisibleLocationResponse
|
|||||||
public string BiomeKey { get; set; } = "plains";
|
public string BiomeKey { get; set; } = "plains";
|
||||||
|
|
||||||
public VisibleLocationObjectResponse? LocationObject { get; set; }
|
public VisibleLocationObjectResponse? LocationObject { get; set; }
|
||||||
|
|
||||||
|
public List<FloorInventoryItemResponse> FloorItems { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user