Initial commit
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user