using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Global.Shared.API; using Global.Shared.Events; using Global.Shared.Plugin; using Sandbox.Game.Entities; using Sandbox.Game.Entities.Cube; using Sandbox.Game.World; using VRage.Collections; using VRage.Game; using VRage.Game.ModAPI; using VRage.Game.ObjectBuilders.Definitions.SessionComponents; using VRage.ObjectBuilders; using VRageMath; namespace Global.Shared.OcTree.Data { public class GridData : IOcTreeData, IGridData { private static readonly Stopwatch _stopwatch = new Stopwatch(); public ConcurrentDictionary Blocks; public MyConcurrentList FunctionalBlocks; public List SubGrids; public GridData(MyCubeGrid grid) { CubeGrid = grid; Id = grid.EntityId; Init(); } public bool IsInVoxel { get; set; } public int BlockCount { get; set; } public MyCubeGrid CubeGrid { get; } public IEnumerable GetBlocks(MyObjectBuilderType type) { // TODO: Improve memory allocation of this. return Blocks.TryGetValue(type, out var list) ? list.Cast().ToArray() : Enumerable.Empty(); } public IEnumerable GetAllFunctionalBlocks() { return FunctionalBlocks ?? Enumerable.Empty(); } public bool Contains(MyObjectBuilderType type) { return Blocks.ContainsKey(type) && Blocks[type].Count > 0; } public void Update() { Recompute(); } public long Id { get; set; } public List ChildIds => SubGrids?.Select(x => x.EntityId).ToList(); public long Flags { get; private set; } public BoundingBoxD BoundingBox { get; private set; } public void Recompute() { Flags = GridFlag.None; BoundingBox = CubeGrid.GetPhysicalGroupAABB(); Flags |= CubeGrid.IsStatic ? GridFlag.StaticGrid : GridFlag.DynamicGrid; Flags |= CubeGrid.GridSizeEnum == MyCubeSize.Large ? GridFlag.LargeGrid : GridFlag.SmallGrid; BlockCount = CubeGrid.BlocksCount + (SubGrids?.Sum(x => x.BlocksCount) ?? 0); } private bool Closing; public void Dispose() { GlobalInstance.Log.Debug($"Disposing of grid data for {Id} {CubeGrid.DisplayName}"); if (GlobalInstance.Log.IsTraceEnabled) GlobalInstance.Log.Trace(new StackTrace(true).ToString()); CubeGrid.OnStaticChanged -= OnStaticChanged; CubeGrid.OnConnectionChanged -= OnConnectionChanged; CubeGrid.OnGridMerge -= OnGridMerged; CubeGrid.OnGridSplit -= OnGridSplit; CubeGrid.OnFatBlockAdded -= AddBlock; CubeGrid.OnFatBlockRemoved -= OnFatBlockRemoved; SubGrids?.ForEach(x => { x.OnStaticChanged -= OnStaticChanged; x.OnConnectionChanged -= OnConnectionChanged; x.OnGridMerge -= OnGridMerged; x.OnGridSplit -= OnGridSplit; x.OnFatBlockAdded -= AddBlock; x.OnFatBlockRemoved -= OnFatBlockRemoved; }); Closing = true; GlobalInstance.Run(() => { Blocks.Clear(); FunctionalBlocks.Clear(); SubGrids.Clear(); }); } private void OnStaticChanged(MyCubeGrid arg1, bool arg2) { RecheckVoxelStatus(); } public void Init() { Blocks = new ConcurrentDictionary(); FunctionalBlocks = new MyConcurrentList(); RecalculateSubgrids(); Recompute(); CubeGrid.OnStaticChanged += OnStaticChanged; CubeGrid.OnConnectionChanged += OnConnectionChanged; CubeGrid.OnGridMerge += OnGridMerged; CubeGrid.OnGridSplit += OnGridSplit; CubeGrid.OnFatBlockAdded += AddBlock; CubeGrid.OnFatBlockRemoved += OnFatBlockRemoved; if (GlobalInstance.Log.IsDebugEnabled) { _stopwatch.Reset(); _stopwatch.Start(); } ScanGridBlocks(CubeGrid); if (SubGrids.Count > 0) SubGrids.ForEach(ScanGridBlocks); RecheckVoxelStatus(); if (GlobalInstance.Log.IsDebugEnabled) GlobalInstance.Log.Debug( $"Data Init took {_stopwatch.ElapsedMilliseconds}ms for {CubeGrid.DisplayName} with {CubeGrid.BlocksCount + SubGrids.Sum(e => e.BlocksCount)} blocks and {SubGrids.Count} subgrids"); } private void OnFatBlockRemoved(MyCubeBlock block) { if (Closing) return; if (Blocks.TryGetValue(block.BlockDefinition.Id.TypeId, out var bag)) bag.Remove(block); GlobalInstance.Run(() => { if (block is IMyControllableEntity entity) { entity.ControllerInfo.ControlAcquired -= OnControlAcquired; entity.ControllerInfo.ControlReleased -= OnControlReleased; OnControlReleased(entity.ControllerInfo.Controller); } BlockEvents.OnBlockRemoved?.Invoke(block); }); } public void RecheckVoxelStatus() { if (Closing) return; var settings = new MyGridPlacementSettings { VoxelPlacement = new VoxelPlacementSettings { PlacementMode = VoxelPlacementMode.Volumetric } }; IsInVoxel = false; if (!CheckGridVoxelStatic(CubeGrid, settings)) if (SubGrids.Count > 0 && SubGrids.All(e => !CheckGridVoxelStatic(e, settings))) return; IsInVoxel = true; } public bool CheckGridVoxelStatic(MyCubeGrid grid, MyGridPlacementSettings settings) { if (!MyCubeGrid.IsAabbInsideVoxel(grid.WorldMatrix, grid.PositionComp.LocalAABB, settings)) return false; var worldAabb = grid.PositionComp.WorldAABB; return MyGamePruningStructure.AnyVoxelMapInBox(ref worldAabb) && grid.GetBlocks().Any(block => MyCubeGrid.IsInVoxels(block, isVolumetric: false)); } private bool RecalculateSubgrids() { var previous = SubGrids; SubGrids = CubeGrid.GetConnectedGrids(GridLinkTypeEnum.Mechanical); SubGrids.Remove(CubeGrid); if (previous == null) { SubGrids.ForEach(OnSubGridAdded); return SubGrids.Any(); } var changed = false; var newSubGrids = SubGrids.Except(previous).ToList(); if (newSubGrids.Count > 0) { changed = true; newSubGrids.ForEach(OnSubGridAdded); } var removedSubGrids = previous.Except(SubGrids).ToList(); if (removedSubGrids.Count > 0) { changed = true; removedSubGrids.ForEach(OnSubGridRemoved); } if (changed) GlobalStatic.OcTreeHandler.GridTree.Update(Id); return changed; } private void OnGridSplit(MyCubeGrid arg1, MyCubeGrid arg2) { GlobalInstance.Log.Debug( $"Grid split Invoke: {arg1.DisplayName} and {arg2.DisplayName} {Thread.CurrentThread.Name}"); GlobalInstance.Run(() => { GlobalInstance.Log.Debug( $"Grid split Run: {arg1.DisplayName} and {arg2.DisplayName} {Thread.CurrentThread.Name}"); GlobalStatic.OcTreeHandler.GridTree.Remove(arg1.EntityId); GlobalStatic.OcTreeHandler.GridTree.Remove(arg2.EntityId); GlobalStatic.OcTreeHandler.AddGrid(arg1); GlobalStatic.OcTreeHandler.AddGrid(arg2); }); } private void OnGridMerged(MyCubeGrid arg1, MyCubeGrid arg2) { //GlobalInstance.Log.Info($"Merging {arg1.DisplayName} and {arg2.DisplayName}"); RecalculateSubgrids(); Recompute(); Blocks.Clear(); FunctionalBlocks.Clear(); ScanGridBlocks(CubeGrid); if (SubGrids.Count > 0) SubGrids.ForEach(ScanGridBlocks); } private void OnConnectionChanged(MyCubeGrid arg1, GridLinkTypeEnum arg2) { if (Closing) return; if (arg2 != GridLinkTypeEnum.Mechanical) return; RecalculateSubgrids(); Recompute(); Blocks.Clear(); FunctionalBlocks.Clear(); ScanGridBlocks(CubeGrid); if (SubGrids.Count > 0) SubGrids.ForEach(ScanGridBlocks); } private void ScanGridBlocks(MyCubeGrid obj) { Parallel.ForEach(obj.GetFatBlocks(), AddBlock); } private void OnSubGridAdded(MyCubeGrid grid) { GlobalStatic.OcTreeHandler.RemoveGrid(grid); grid.OnFatBlockAdded += AddBlock; grid.OnFatBlockRemoved += OnFatBlockRemoved; grid.OnStaticChanged += OnStaticChanged; grid.OnConnectionChanged += OnConnectionChanged; grid.OnGridMerge += OnGridMerged; grid.OnGridSplit += OnGridSplit; GridEvents.SubGridAdded?.Invoke(CubeGrid, grid); RecheckVoxelStatus(); } private void OnSubGridRemoved(MyCubeGrid grid) { grid.OnFatBlockAdded -= AddBlock; grid.OnFatBlockRemoved -= OnFatBlockRemoved; grid.OnStaticChanged -= OnStaticChanged; grid.OnConnectionChanged -= OnConnectionChanged; grid.OnGridMerge -= OnGridMerged; grid.OnGridSplit -= OnGridSplit; GridEvents.SubGridRemoved?.Invoke(CubeGrid, grid); RecheckVoxelStatus(); } private void AddBlock(MyCubeBlock block) { if (Closing) return; Blocks.AddOrUpdate(block.BlockDefinition.Id.TypeId, _ => ArrayList.Synchronized(new ArrayList { block }), (_, list) => { list.Add(block); return list; }); GlobalInstance.Run(() => { if (block is IMyControllableEntity entity) { entity.ControllerInfo.ControlAcquired += OnControlAcquired; entity.ControllerInfo.ControlReleased += OnControlReleased; if (entity.ControllerInfo.IsLocallyControlled() || entity.ControllerInfo.IsRemotelyControlled()) OnControlAcquired(entity.ControllerInfo.Controller); } if (block is MyFunctionalBlock functionalBlock) FunctionalBlocks.Add(functionalBlock); BlockEvents.OnBlockAdded?.Invoke(block); }); } private void OnControlAcquired(MyEntityController obj) { BlockEvents.OnPlayerControlAcquired?.Invoke(obj.ControlledEntity); } private void OnControlReleased(MyEntityController obj) { if (obj != null) BlockEvents.OnPlayerControlReleased?.Invoke(obj.ControlledEntity); } public void RaiseAttachedEntityChanged() { OnConnectionChanged(CubeGrid, GridLinkTypeEnum.Mechanical); } } }