override protected bool HandleIntersectionWithBatchedStroke(BatchSubset rGroup) { if (!rGroup.m_Active) { // Subset has already been deleted. // Collision detection is async and has latency, so theoretically we should expect this case. // However, in practice it's currently not possible; so flag it as unexpected. Debug.LogWarningFormat( "{0}: Unexpected: deleting already-deleted stroke @ {1}", rGroup.m_ParentBatch.ParentPool.Name, Time.frameCount); return(false); } if (altSelect) { if (m_BatchFilter == null && rGroup.m_ParentBatch != null) { m_BatchFilter = rGroup.m_ParentBatch; } if (!ReferenceEquals(m_BatchFilter, rGroup.m_ParentBatch)) { return(true); } } else { m_BatchFilter = null; } SketchMemoryScript.m_Instance.MemorizeDeleteSelection(rGroup.m_Stroke); PlayModifyStrokeSound(); return(true); }
public void MemorizeBatchedBrushStroke( BatchSubset subset, Color rColor, Guid brushGuid, float fBrushSize, float brushScale, List <PointerManager.ControlPoint> rControlPoints, StrokeFlags strokeFlags, StencilWidget stencil, float lineLength, int seed) { // NOTE: PointerScript calls ClearRedo() in batch case Stroke rNewStroke = new Stroke(); rNewStroke.m_Type = Stroke.Type.BatchedBrushStroke; rNewStroke.m_BatchSubset = subset; rNewStroke.m_ControlPoints = rControlPoints.ToArray(); rNewStroke.m_ControlPointsToDrop = new bool[rNewStroke.m_ControlPoints.Length]; rNewStroke.m_Color = rColor; rNewStroke.m_BrushGuid = brushGuid; rNewStroke.m_BrushSize = fBrushSize; rNewStroke.m_BrushScale = brushScale; rNewStroke.m_Flags = strokeFlags; rNewStroke.m_Seed = seed; subset.m_Stroke = rNewStroke; SketchMemoryScript.m_Instance.RecordCommand( new BrushStrokeCommand(rNewStroke, stencil, lineLength)); if (m_SanityCheckStrokes) { SanityCheckGeometryGeneration(rNewStroke); //SanityCheckVersusReplacementBrush(rNewObject); } MemoryListAdd(rNewStroke); TiltMeterScript.m_Instance.AdjustMeter(rNewStroke, up: true); }
public void DisableSubset(BatchSubset subset) { SelfCheck(); if (!subset.m_Active) { return; } Debug.Assert(subset.m_Active); subset.m_Active = false; if (subset.m_TriangleBackup == null) { subset.m_TriangleBackup = new ushort[subset.m_nTriIndex]; } m_Geometry.EnsureGeometryResident(); var aTris = m_Geometry.m_Tris.GetBackingArray(); int t0 = subset.m_iTriIndex; int t1 = subset.m_iTriIndex + subset.m_nTriIndex; // TODO: Possibly could optimize this in C++ for 4.4% of time in selection. for (int t = t0; t < t1; ++t) { subset.m_TriangleBackup[t - t0] = (ushort)aTris[t]; aTris[t] = 0; } m_bTopologyDirty = true; SelfCheck(); }
override protected bool HandleIntersectionWithBatchedStroke(BatchSubset rGroup) { if (altSelect) { if (m_BatchFilter == null && rGroup.m_ParentBatch != null) { m_BatchFilter = rGroup.m_ParentBatch; } if (!ReferenceEquals(m_BatchFilter, rGroup.m_ParentBatch)) { return(true); } } else { m_BatchFilter = null; } var didRepaint = SketchMemoryScript.m_Instance.MemorizeStrokeRepaint( rGroup.m_Stroke, RecolorOn, RebrushOn, ResizeOn, JitterOn); if (didRepaint) { PlayModifyStrokeSound(); } return(didRepaint); }
override protected bool HandleIntersectionWithBatchedStroke(BatchSubset rGroup) { #if (UNITY_EDITOR || EXPERIMENTAL_ENABLED) if (altSelect && Config.IsExperimental) { if (m_BatchFilter == null && rGroup.m_ParentBatch != null) { m_BatchFilter = rGroup.m_ParentBatch; } if (!ReferenceEquals(m_BatchFilter, rGroup.m_ParentBatch)) { return(true); } } else { m_BatchFilter = null; } #endif var didRepaint = SketchMemoryScript.m_Instance.MemorizeStrokeRepaint( rGroup.m_Stroke, m_Recolor, m_Rebrush); if (didRepaint) { PlayModifyStrokeSound(); } return(didRepaint); }
/// Like BaseBrushScript.CloneAsUndoObject(), except: /// - to avoid waste, use a pre-instantiated object /// - assume object already has UndoMeshAnimScript, doesn't have BaseBrushScript public void CloneAsUndoObject(BatchSubset subset, GameObject clone) { // GameObject clone = Instantiate<GameObject>(...); premade for us // clone.name = ...; subset.m_ParentBatch.CopyToMesh(subset, clone); clone.transform.parent = m_ParentTransform; Coords.AsLocal[clone.transform] = TrTransform.identity; clone.SetActive(true); clone.GetComponent <UndoMeshAnimScript>().Init(); }
override protected bool HandleIntersectionWithBatchedStroke(BatchSubset rGroup) { #if (UNITY_EDITOR || EXPERIMENTAL_ENABLED) if (altSelect && Config.IsExperimental) { if (m_BatchFilter == null && rGroup.m_ParentBatch != null) { m_BatchFilter = rGroup.m_ParentBatch; } if (!ReferenceEquals(m_BatchFilter, rGroup.m_ParentBatch)) { return(true); } } else { m_BatchFilter = null; } #endif var stroke = rGroup.m_Stroke; var isSelected = SelectionManager.m_Instance.IsStrokeSelected(stroke); bool removeFromSelection = SelectionManager.m_Instance.ShouldRemoveFromSelection(); if ((removeFromSelection && !isSelected) || (!removeFromSelection && isSelected)) { // I think it's actually expected that this happens every now and then. // The intersection results are from some time in the past. Debug.LogWarning( "Attempted to " + (removeFromSelection ? "deselect" : "select") + " a stroke that's already " + (isSelected ? "selected" : "deselected") + "."); return(true); } PlayModifyStrokeSound(); SketchMemoryScript.m_Instance.PerformAndRecordCommand( new SelectCommand(new[] { stroke }, null, SelectionManager.m_Instance.SelectionTransform, initial: !m_ActiveSelectionHasAtLeastOneObject, deselect: removeFromSelection)); m_ActiveSelectionHasAtLeastOneObject = true; m_LastIntersectionTime = Time.realtimeSinceStartup; // If we're selecting strokes while an existing selection has been transformed, // create a new selection and consolidate the command for selecting the future strokes // with the command to deselect the prior selection. if (!removeFromSelection && SelectionManager.m_Instance.SelectionWasTransformed) { EndSelection(); } return(true); }
override protected bool HandleIntersectionWithBatchedStroke(BatchSubset rGroup) { var didRepaint = SketchMemoryScript.m_Instance.MemorizeStrokeRepaint( rGroup.m_Stroke, m_Recolor, m_Rebrush); if (didRepaint) { PlayModifyStrokeSound(); } return(didRepaint); }
/// Creates and returns a new subset containing the passed geometry. /// Pass: /// otherSubset - /// Specifies both the material/batch to copy into, /// as well as the geometry to copy. /// May be owned by a different BatchManager. /// leftTransform - optional transform to transform the subset. public BatchSubset CreateSubset(BatchSubset otherSubset, TrTransform?leftTransform = null) { Batch otherBatch = otherSubset.m_ParentBatch; BatchPool otherPool = otherBatch.ParentPool; var pool = GetPool(otherPool.m_BrushGuid); var batch = GetBatch(pool, otherSubset.m_VertLength); return(batch.AddSubset( otherBatch.Geometry, otherSubset.m_StartVertIndex, otherSubset.m_VertLength, otherSubset.m_iTriIndex, otherSubset.m_nTriIndex, leftTransform)); }
public void CopyToMesh(BatchSubset subset, GameObject obj) { SelfCheck(); int iVert = subset.m_StartVertIndex; int nVert = subset.m_VertLength; m_Geometry.CopyToMesh(obj.GetComponent <MeshFilter>().mesh, iVert, nVert, subset.m_iTriIndex, subset.m_nTriIndex); MeshRenderer objRenderer = obj.GetComponent <MeshRenderer>(); // Temporary workaround for b/31346571 objRenderer.material = GetComponent <MeshRenderer>().material; }
private void AdjustMeter(BatchSubset subset, float fBrushSize, bool bAddToMeter) { //make brush size not matter if we're ignorning it if (!m_BrushSizeAffectsCost) { fBrushSize = 1.0f; } //figure out how much this batch costs BrushDescriptor brush = BrushCatalog.m_Instance.GetBrush( subset.m_ParentBatch.ParentPool.m_BrushGuid); float fCost = BaseBrushScript.GetStrokeCost(brush, subset.m_VertLength, fBrushSize); AdjustMeter(bAddToMeter ? fCost : -fCost); }
static void WriteStroke(JsonWriter json, Stroke stroke, Dictionary <Guid, int> brushMap) { json.WriteStartObject(); var brushGuid = stroke.m_BrushGuid; BrushDescriptor desc = BrushCatalog.m_Instance.GetBrush(brushGuid); int brushIndex; if (!brushMap.TryGetValue(brushGuid, out brushIndex)) { brushIndex = brushMap.Count; brushMap[brushGuid] = brushIndex; } json.WritePropertyName("brush"); json.WriteValue(brushIndex); if (stroke.m_Type == Stroke.Type.BrushStroke) { // Some strokes (eg particles) don't have meshes. For now, assume that // if the stroke has a mesh, it should be written. var meshFilter = stroke.m_Object.GetComponent <MeshFilter>(); if (meshFilter != null) { var mesh = meshFilter.sharedMesh; if (mesh != null) { WriteMesh(json, mesh, desc.VertexLayout); } } } else if (stroke.m_Type == Stroke.Type.BatchedBrushStroke) { BatchSubset subset = stroke.m_BatchSubset; GeometryPool geom = subset.m_ParentBatch.Geometry; Mesh tempMesh = new Mesh(); geom.CopyToMesh(tempMesh, subset.m_StartVertIndex, subset.m_VertLength, subset.m_iTriIndex, subset.m_nTriIndex); WriteMesh(json, tempMesh, geom.Layout); tempMesh.Clear(); UnityEngine.Object.Destroy(tempMesh); } json.WriteEndObject(); }
public void RemoveSubset(BatchSubset subset) { // Often O(1) because it's the last one SelfCheck(); int iSubset = m_Groups.LastIndexOf(subset); if (iSubset < 0) { Debug.Assert(false, "Not found"); return; } // Could do some compaction, but this case is not very common. // Just disable the triangles. If all subsets after this one are // freed, we'll reclaim the space then. DisableSubset(subset); // If this is the last subset, we can free up some space. if (iSubset == m_Groups.Count - 1) { // It would be incorrect to simply subtract from Num{Verts,TriIndices}, because // there may be dead space before this subset. Debug.Assert(subset.m_StartVertIndex + subset.m_VertLength == m_Geometry.NumVerts); Debug.Assert(subset.m_iTriIndex + subset.m_nTriIndex == m_Geometry.NumTriIndices); int newNumVert, newNumIndices; if (iSubset > 0) { var prev = m_Groups[iSubset - 1]; newNumVert = prev.m_StartVertIndex + prev.m_VertLength; newNumIndices = prev.m_iTriIndex + prev.m_nTriIndex; } else { newNumVert = newNumIndices = 0; } m_Geometry.NumVerts = newNumVert; m_Geometry.NumTriIndices = newNumIndices; DelayedUpdateMesh(); } m_Groups.RemoveAt(iSubset); subset.m_ParentBatch = null; SelfCheck(); }
/// Returns a new subset containing the passed geometry. /// raises ArgumentOutOfRangeException if not enough room. /// Pass: /// rMasterBrush - Geometry, all of which will be copied into the new subset public BatchSubset AddSubset(int nVert, int nTris, MasterBrush rMasterBrush) { SelfCheck(); // If we're not empty, the caller should never have tried to add the subset, // because it's caller's responsibility to check HasSpaceFor(). // If we're empty, allow anything (up to the Unity limit). if (!HasSpaceFor(nVert) && (m_Geometry.NumVerts > 0)) { throw new ArgumentOutOfRangeException("nVert"); } if (m_Geometry.NumVerts == 0) { m_Geometry.Layout = rMasterBrush.VertexLayout.Value; } BatchSubset child = new BatchSubset(); child.m_ParentBatch = this; m_Groups.Add(child); child.m_Active = true; child.m_StartVertIndex = m_Geometry.NumVerts; child.m_VertLength = nVert; child.m_iTriIndex = m_Geometry.NumTriIndices; child.m_nTriIndex = nTris; // This is normally true -- unless the geometry has been welded // Debug.Assert(nVert % 3 == 0); child.m_Bounds = GetBoundsFor(rMasterBrush.m_Vertices, 0, nVert); if (nVert > 0) { m_Geometry.EnsureGeometryResident(); AppendVertexData(nVert, rMasterBrush.m_Vertices, rMasterBrush.m_Normals, rMasterBrush.m_UVs, rMasterBrush.m_UVWs, rMasterBrush.m_Colors, rMasterBrush.m_Tangents); AppendTriangleData(child.m_StartVertIndex, child.m_nTriIndex, rMasterBrush.m_Tris); DelayedUpdateMesh(); } SelfCheck(); return(child); }
public void EnableSubset(BatchSubset subset) { SelfCheck(); if (subset.m_Active) { return; } Debug.Assert(!subset.m_Active); subset.m_Active = true; m_Geometry.EnsureGeometryResident(); var aTris = m_Geometry.m_Tris.GetBackingArray(); int t0 = subset.m_iTriIndex; int t1 = subset.m_iTriIndex + subset.m_nTriIndex; for (int t = t0; t < t1; ++t) { aTris[t] = subset.m_TriangleBackup[t - t0]; } m_bTopologyDirty = true; SelfCheck(); }
/// Returns a new subset containing the passed geometry. /// raises ArgumentOutOfRangeException if not enough room. /// /// Make sure the triangle indices refer only to verts inside the /// passed range of verts. /// /// Pass: /// geom - Geometry, a subset of which will be copied into the new subset /// i/nVert - start/count of verts to copy /// i/nTriIndex - start/count of triangle indices to copy. /// leftTransform - optional transform to transform the subset. /// public BatchSubset AddSubset( GeometryPool geom, int iVert, int nVert, int iTriIndex, int nTriIndex, TrTransform?leftTransform = null) { // If we're not empty, the caller should never have tried to add the subset, // because it's caller's responsibility to check HasSpaceFor(). // If we're empty, allow anything (up to the Unity limit). SelfCheck(); if (!HasSpaceFor(nVert) && (m_Geometry.NumVerts > 0)) { throw new ArgumentOutOfRangeException("nVert"); } if (m_Geometry.NumVerts == 0) { m_Geometry.Layout = geom.Layout; } BatchSubset child = new BatchSubset(); child.m_ParentBatch = this; m_Groups.Add(child); child.m_Active = true; child.m_StartVertIndex = m_Geometry.NumVerts; child.m_VertLength = nVert; child.m_iTriIndex = m_Geometry.NumTriIndices; child.m_nTriIndex = nTriIndex; geom.EnsureGeometryResident(); child.m_Bounds = GetBoundsFor(geom.m_Vertices, iVert, nVert, leftTransform); if (nVert > 0) { m_Geometry.Append(geom, iVert, nVert, iTriIndex, nTriIndex, leftTransform); DelayedUpdateMesh(); } SelfCheck(); return(child); }
// Generate geometry from stroke as if it were being played back. // If played-back geometry is different, complain and make the new stroke visible. // TODO: do more kinds of brushes private static void SanityCheckGeometryGeneration(Stroke oldStroke) { if (oldStroke.m_Type == Stroke.Type.BrushStroke) { if (oldStroke.m_Object.GetComponent <MeshFilter>() == null) { // Stroke can't be checked return; } } { BrushDescriptor desc = BrushCatalog.m_Instance.GetBrush(oldStroke.m_BrushGuid); if (desc == null || desc.m_Nondeterministic) { // Stroke doesn't want to be checked return; } } // Re-create geometry. PointerManager's pointer management is a complete mess. // "5" is the most-likely to be unused. var pointer = PointerManager.m_Instance.GetTransientPointer(5); // Make a copy, since Begin/EndLineFromMemory mutate little bits of MemoryBrushStroke Stroke newStroke = new Stroke(); newStroke.m_BrushGuid = oldStroke.m_BrushGuid; newStroke.m_ControlPoints = oldStroke.m_ControlPoints; newStroke.m_BrushScale = oldStroke.m_BrushScale; newStroke.m_BrushSize = oldStroke.m_BrushSize; newStroke.m_Color = oldStroke.m_Color; newStroke.m_Seed = oldStroke.m_Seed; // Now swap r and b newStroke.m_Color.r = oldStroke.m_Color.b; newStroke.m_Color.b = oldStroke.m_Color.r; Array.Copy(oldStroke.m_ControlPointsToDrop, newStroke.m_ControlPointsToDrop, oldStroke.m_ControlPointsToDrop.Length); newStroke.m_Object = pointer.BeginLineFromMemory(newStroke, oldStroke.Canvas); pointer.UpdateLineFromStroke(newStroke); // Compare geometry string err; var newBrush = pointer.CurrentBrushScript; if (oldStroke.m_Type == Stroke.Type.BrushStroke) { Mesh oldMesh = oldStroke.m_Object.GetComponent <MeshFilter>().sharedMesh; err = Compare(oldMesh, newBrush); } else { BatchSubset oldMesh = oldStroke.m_BatchSubset; GeometryPool allGeom = oldMesh.m_ParentBatch.Geometry; Vector3[] verts = allGeom.m_Vertices.GetBackingArray(); int[] tris = allGeom.m_Tris.GetBackingArray(); // To avoid super complications, only check in the common case of // uv0 == Vector2[] Vector2[] uv0; if (allGeom.Layout.texcoord0.size == 2) { uv0 = allGeom.m_Texcoord0.v2.GetBackingArray(); } else { uv0 = null; } err = Compare( verts, oldMesh.m_StartVertIndex, oldMesh.m_StartVertIndex + oldMesh.m_VertLength, tris, oldMesh.m_iTriIndex, oldMesh.m_iTriIndex + oldMesh.m_nTriIndex, uv0, newBrush); } if (err != null) { // Use assert so it shows up in ExceptionRenderScript UnityEngine.Debug.Assert(false, string.Format("Sanity check stroke: {0}", err)); newBrush.ApplyChangesToVisuals(); // Turn off batching for this stroke because our batching system currently // can't handle strokes that aren't on the undo stack, in sketch memory, etc bool prevValue = App.Config.m_UseBatchedBrushes; App.Config.m_UseBatchedBrushes = false; pointer.EndLineFromMemory(newStroke, discard: false); App.Config.m_UseBatchedBrushes = prevValue; } else { pointer.EndLineFromMemory(newStroke, discard: true); } }
public static void InitUndoObject(BatchSubset subset) { // TODO: Finish moving control of prefab into BatchManager subset.Canvas.BatchManager.CloneAsUndoObject( subset, m_Instance.m_UndoBatchMesh); }
/// An alternative to overriding HandleIntersection(), /// if subclasses need finer-grained control /// Returns true if an action was carried out as a result of the intersection. /// /// The subset is guaranteed to be valid (ie, not part of a deleted batch) /// but you should still check if its state is correct (ie, if you're erasing, /// that it's not already erased) virtual protected bool HandleIntersectionWithBatchedStroke(BatchSubset rGroup) { HandleIntersection(rGroup.m_Stroke); return(true); }
/// Side effects: /// - May cause HandleIntersectionWithXxx to be called /// /// Returns true if and only if any "intersection actions" were carried out (ie, /// if at least one HandleIntersectionWithXxx call returned true) /// private bool UpdateGpuIntersection(Vector3 vDetectionCenter_GS, float size_GS) { // Possible states of m_GpuFutureResult and m_GpuFutureResultlist: // // m_GpuFutureResult = null No outstanding GPU request. m_GpuFutureResultList // is unused, might contain garbage, and is ready to pass // to a new GpuFutureResult // m_GpuFutureResult != null // IsReady = false Request is running; will fill in m_GpuFutureResultList // IsReady = true Request is done; m_GpuFutureResultList is filled in // // Possible states of m_GpuOldResultList and m_GpuConsumedResults: // // m_GpuOldResultList Always not-null, but may have been fully-processed // m_GpuConsumedResults Indices >= this have yet to be consumed // // m_GpuFutureResult may be pending for multiple frames. // m_GpuOldResultList may be processed over multiple frames. if (m_GpuFutureResult == null) { // Note that m_GpuFutureResultList will be cleared and populated at some future point // after this call. int intersectionLayer = (1 << m_CurrentCanvas.gameObject.layer) | AdditionalGpuIntersectionLayerMasks(); // The new request will only be null when the intersector is disabled. // Given the logic in this function, this should be fine without any special handling. // TODO: use a pool of List<BatchResult> instead of being so stateful m_GpuFutureResult = App.Instance.GpuIntersector .RequestBatchIntersections(vDetectionCenter_GS, size_GS, m_GpuFutureResultList, 255, intersectionLayer); } else if (m_GpuFutureResult.IsReady) { // We could go use GpuResultList, but as we're swapping the buffers here, it feels better // to be explicit about which buffers we're swapping. // TODO: use m_GpuFutureResult.GetResults() instead List <GpuIntersector.BatchResult> results = m_GpuFutureResultList; m_GpuFutureResultList = m_GpuOldResultList; // Note that this throws away any results that have yet to be consumed. m_GpuOldResultList = results; m_GpuConsumedResults = 0; // We could immediately submit another request, however we have likely already hit our budget // when there are intersections, so it should generally feel better to allow this to be a // three frame cycle. m_GpuFutureResult = null; } if (m_GpuConsumedResults < m_GpuOldResultList.Count) { int hitCount = 0; for (int i = m_GpuConsumedResults; i < m_GpuOldResultList.Count && !m_TimesUp; i++) { // Prefer to find widgets, although the results struct should never have both. if (m_GpuOldResultList[i].widget) { if (HandleIntersectionWithWidget(m_GpuOldResultList[i].widget)) { hitCount++; } } else { BatchSubset subset = m_GpuOldResultList[i].subset; if (subset.m_ParentBatch == null) { // The stroke was deleted between creating the result and processing the result. This // could happen due to the inherent latency in GPU intersection, although in practice, // this should be very rare. But this will also happen if the selection tool intersects // more than a single stroke in the same group. In this case, the following happens: // // * HandleIntersectionWithBatchedStroke() is called once with one of the strokes in // the group. // * All the strokes in that group are moved to the selection canvas and thus, a // different subset. // * HandleIntersectionWithBatchedStroke() is called once with another stroke in the // same group. continue; } if (HandleIntersectionWithBatchedStroke(subset)) { hitCount++; } } // Always process at least 1 hit. This number can be tuned to taste, but in initial // tests, it kept the deletion time under the frame budget while still feeling responsive. if (hitCount > 0) { m_TimesUp = m_DetectionStopwatch.ElapsedTicks > m_TimeSliceInTicks; } } m_GpuConsumedResults += hitCount; if (hitCount > 0) { return(true); } } return(false); }
// ------------------------------------------------------------------------------------------ // // Private Internals // ------------------------------------------------------------------------------------------ // override protected void OnReadResults() { // Mark results as ready. m_ResultCount = 0; if (m_ResultList != null) { m_ResultList.Clear(); } uint[] resultColors = GetTextureColors(); // // Process the color buffer into result objects, if requested. // UnityEngine.Profiling.Profiler.BeginSample("Intersection: Process Results"); HashSet <object> seen = AllocateHashSet(); uint c; for (int i = 0; i < resultColors.Length; i++) { if (m_ResultCount == m_MaxResults) { break; } c = resultColors[i]; if (c > 0) { // Don't bother looking up the batch if the exact result set wasn't requested. if (m_ResultList == null) { m_ResultCount++; continue; } // TODO: the Color32 -> object lookup is deterministic and O(n), so 'seen' // should store the Color32, not the object. int triIndex = (int)(c & 0xffff) * 3; ushort batchId = (ushort)((c & 0xffff0000) >> 16); GrabWidget widget = null; Batch batch = null; BatchSubset subset = null; // See if this batch refers to a brush stroke. batch = App.ActiveCanvas.BatchManager.GetBatch(batchId); if (batch != null) { // TODO: move this into Batch, so can do binary search if necessary for (int j = 0; j < batch.m_Groups.Count; j++) { BatchSubset bs = batch.m_Groups[j]; if (triIndex >= bs.m_iTriIndex && triIndex < bs.m_iTriIndex + bs.m_nTriIndex) { subset = bs; break; } } // A stroke may be deleted by the time this executes. This is due to the delay between // sending an intersection request and processing the results. if (subset == null) { // This actually happens in practice! // TODO: investigate if it's okay // Debug.LogWarningFormat( // "Unexpected: Nonexistent subset for c = {0:x} {1:x}", // (ushort)(c >> 16), // (ushort)(c & 0xffff)); continue; } if (!seen.Add(subset)) { continue; } } else { // Not a brush stroke? See if this is a widget. widget = WidgetManager.m_Instance.GetBatch(batchId); // A widget may be deleted by the time this executes. This is due to the delay between // sending an intersection request and processing the results. if (widget == null) { continue; } if (!seen.Add(widget)) { continue; } } // A batch should never be null, but in the future that may change. This is possible due // to the delay between sending an intersection request and processing the results. if (batch == null && widget == null) { Debug.LogWarningFormat( "Unexpected: Null batch {0} and widget {1}", ReferenceEquals(batch, null), ReferenceEquals(widget, null)); continue; } // These cannot both be valid. Debug.Assert(subset == null || widget == null); m_ResultList.Add( new BatchResult { widget = widget, subset = subset }); m_ResultCount++; } } DeallocateHashSet(seen); UnityEngine.Profiling.Profiler.EndSample(); }