Files
BQP/srcs/RobotNet10/Commons/RobotNet10.MapManager/Services/LayoutLevelNamingService.cs
2026-07-13 09:25:40 +07:00

174 lines
6.3 KiB
C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using RobotNet10.MapManager.Data;
namespace RobotNet10.MapManager.Services;
/// <summary>
/// Service for generating unique node and edge names using 8-character GUIDs
/// Concurrent-safe and optimized for Import/Export scenarios
/// </summary>
public class LayoutLevelNamingService(MapDbContext context)
{
private readonly MapDbContext _context = context;
private const int GUID_LENGTH = 8;
private const int MAX_RETRIES = 5;
/// <summary>
/// Generate unique node name using 8-character GUID
/// Format: "Node_a7f2e3b1"
/// </summary>
/// <param name="levelId">Layout level ID</param>
/// <returns>Generated node name or empty string if auto-generate is disabled</returns>
/// <exception cref="InvalidOperationException">If cannot generate unique name after retries</exception>
public async Task<string> GenerateNodeNameAsync(Guid levelId)
{
var settings = await GetOrCreateSettingsAsync(levelId);
if (!settings.NodeNameAutoGenerate)
return string.Empty;
// Try to generate unique name with retries
for (int attempt = 0; attempt < MAX_RETRIES; attempt++)
{
var guid = Guid.NewGuid().ToString("N"); // No hyphens
var shortGuid = guid[..GUID_LENGTH];
var nodeName = $"N_{shortGuid}";
// Check uniqueness (indexed query, very fast)
bool exists = await _context.Nodes
.AnyAsync(n => n.LevelId == levelId && n.NodeName == nodeName);
if (!exists) return nodeName;
}
// Extremely unlikely to reach here (probability < 0.00001%)
throw new InvalidOperationException(
$"Failed to generate unique node name after {MAX_RETRIES} attempts. " +
"This is extremely unlikely. Please contact system administrator.");
}
/// <summary>
/// Generate unique edge name using 8-character GUID
/// Format: "Edge_a7f2e3b1"
/// </summary>
/// <param name="levelId">Layout level ID</param>
/// <returns>Generated edge name or empty string if auto-generate is disabled</returns>
/// <exception cref="InvalidOperationException">If cannot generate unique name after retries</exception>
public async Task<string> GenerateEdgeNameAsync(Guid levelId)
{
var settings = await GetOrCreateSettingsAsync(levelId);
if (!settings.EdgeNameAutoGenerate)
return string.Empty;
// Try to generate unique name with retries
for (int attempt = 0; attempt < MAX_RETRIES; attempt++)
{
var guid = Guid.NewGuid().ToString("N"); // No hyphens
var shortGuid = guid.Substring(0, GUID_LENGTH);
var edgeName = $"E_{shortGuid}";
// Check uniqueness (indexed query, very fast)
bool exists = await _context.Edges
.AnyAsync(e => e.LevelId == levelId && e.EdgeName == edgeName);
if (!exists) return edgeName;
}
// Extremely unlikely to reach here (probability < 0.00001%)
throw new InvalidOperationException(
$"Failed to generate unique edge name after {MAX_RETRIES} attempts. " +
"This is extremely unlikely. Please contact system administrator.");
}
/// <summary>
/// Preview example generated names
/// </summary>
/// <param name="count">Number of examples to generate</param>
/// <returns>Array of example names</returns>
public static string[] PreviewNodeNames(int count = 5)
{
var examples = new string[count];
for (int i = 0; i < count; i++)
{
var guid = Guid.NewGuid().ToString("N");
examples[i] = $"Node_{guid.Substring(0, GUID_LENGTH)}";
}
return examples;
}
/// <summary>
/// Preview example generated edge names
/// </summary>
/// <param name="count">Number of examples to generate</param>
/// <returns>Array of example names</returns>
public static string[] PreviewEdgeNames(int count = 5)
{
var examples = new string[count];
for (int i = 0; i < count; i++)
{
var guid = Guid.NewGuid().ToString("N");
examples[i] = $"Edge_{guid.Substring(0, GUID_LENGTH)}";
}
return examples;
}
/// <summary>
/// Get or create editor settings for a layout level
/// </summary>
private async Task<LayoutLevelEditorSettings> GetOrCreateSettingsAsync(Guid levelId)
{
var settings = await _context.LayoutLevelEditorSettings
.FirstOrDefaultAsync(s => s.LevelId == levelId);
if (settings == null)
{
// Auto-create settings with defaults if not exists
settings = new LayoutLevelEditorSettings
{
LevelId = levelId,
// Defaults are set in the entity class
};
_context.LayoutLevelEditorSettings.Add(settings);
await _context.SaveChangesAsync();
}
return settings;
}
/// <summary>
/// Update editor settings for a layout level
/// </summary>
public async Task UpdateSettingsAsync(Guid levelId, Action<LayoutLevelEditorSettings> updateAction)
{
var settings = await GetOrCreateSettingsAsync(levelId);
updateAction(settings);
settings.ModifiedDate = DateTime.UtcNow;
await _context.SaveChangesAsync();
}
/// <summary>
/// Get current editor settings (read-only)
/// </summary>
public async Task<LayoutLevelEditorSettings?> GetSettingsAsync(Guid levelId)
{
return await _context.LayoutLevelEditorSettings
.AsNoTracking()
.FirstOrDefaultAsync(s => s.LevelId == levelId);
}
/// <summary>
/// Get collision statistics (for monitoring)
/// </summary>
public async Task<(int TotalNodes, int TotalEdges)> GetLevelStatisticsAsync(Guid levelId)
{
var nodeCount = await _context.Nodes.CountAsync(n => n.LevelId == levelId);
var edgeCount = await _context.Edges.CountAsync(e => e.LevelId == levelId);
return (nodeCount, edgeCount);
}
}