Class MarkerStore
The bulk-tier marker collection: a flat array of MarkerInstanceData plus a free list, sized for tens of thousands of markers uploaded to the GPU as one buffer.
public sealed class MarkerStore
- Inheritance
-
objectMarkerStore
Remarks
This is the data-only marker tier. There is no GameObject and no component per marker; a marker is an index into an array, and the whole array is handed to a StructuredBuffer for a single instanced draw. Use the prefab tier instead when you need a few hundred markers that carry their own scene objects.
Markers are stored in mercator and go through the same per-frame transform as tiles, so they stay pinned to the map rather than by a follow-up correction.
Zero-GC by contract: mutating a marker allocates nothing, and the
backing array only ever grows, by doubling, when
Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode) runs out of slots. Pass a realistic
initialCapacity to the constructor to avoid the copy. This is
also why Instances hands out the live array rather than a
copy.
Not thread safe, and not safe against concurrent enumeration: any mutation may resize or reuse slots.
Constructors
MarkerStore(int)
Creates an empty store with room for initialCapacity
markers before the first grow.
public MarkerStore(int initialCapacity)
Parameters
initialCapacityintSlots to preallocate. Values below 4 are raised to 4. Growing later costs one array copy, so size this for the expected marker count.
Fields
DefaultCapacityHint
Pre-allocation hint: how many markers a fresh store expects before growing.
public const int DefaultCapacityHint = 256
Field Value
MaxGeneration
Times one slot can be reused before it is retired. A slot whose generation space is spent is never returned to the free list, so a generation can never wrap back onto an id already handed out.
public const int MaxGeneration = 4095
Field Value
MaxSlots
Slots this store can ever hand out. Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode) throws past it rather than let a slot index collide with the generation field.
public const int MaxSlots = 524288
Field Value
MinGpuBufferCapacity
Smallest GPU instance buffer a marker layer allocates.
public const int MinGpuBufferCapacity = 64
Field Value
Properties
ActiveRange
Exclusive upper bound on slot indices ever handed out, and the range the GPU uploads. Slots below this may be live or free; hidden and removed markers are uploaded too and collapsed by the vertex shader. Never decreases, not even when every marker is removed.
public int ActiveRange { get; }
Property Value
Capacity
Slots currently allocated, including free ones.
public int Capacity { get; }
Property Value
Count
Number of live markers, which is not an upper bound on slot indices because removed slots leave gaps. Iterate to ActiveRange, not to this.
public int Count { get; }
Property Value
CurrentSampler
Ground-height source Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode) and UpdatePosition(int, LatLon, double)
resolve altitude against right now. Not a per-call parameter: the
owning LansMap.Markers.Bulk.MarkerLayer (Runtime
assembly) keeps this in sync with the map's own elevation sampler,
the same way SpatialIndex is set once and read by
every later mutation. Null, the default, means flat (0 m) - the
same "no sampler means flat map" convention
ResolveAltitudeMercator(MarkerAltitudeMode, double, LatLon, IElevationSampler) and
MapViewTransform already use.
public IElevationSampler CurrentSampler { get; set; }
Property Value
Dirty
True when a marker changed since the last ClearDirty() and the GPU buffer needs re-uploading. The renderer polls this so that a static set of markers costs nothing per frame.
public bool Dirty { get; }
Property Value
DirtyRangeCount
Number of contiguous slots from DirtyRangeStart that
need re-uploading. This is a bounding range, not a sparse set: a
slot between the first and last touch that did not itself change
is still included, so the renderer can do one contiguous
SetData call instead of one per touched slot. Zero when
Dirty is false.
public int DirtyRangeCount { get; }
Property Value
DirtyRangeStart
Start of the slot range touched since the last ClearDirty(), inclusive. Only meaningful when Dirty is true; the renderer should check Dirty first.
public int DirtyRangeStart { get; }
Property Value
Instances
The live marker data, read-only to callers outside this assembly. Only indices below ActiveRange hold meaningful data, and the span is invalidated by any Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode) that grows the backing array (re-read this property after such a call rather than keeping an old span around).
public ReadOnlySpan<MarkerInstanceData> Instances { get; }
Property Value
Remarks
Indexed by slot, not by marker id: pass ids through SlotOf(int) first. A marker id also carries a generation, so using one as an index reads the wrong slot or throws.
A ReadOnlySpan<T> indexer returns by value, the same
reason transform.position.x = 5 does not compile on
Vector3: Store.Instances[id].Field = x no longer
compiles for an outside caller, closing off the raw-write bug this
property used to allow (writing AltitudeMercator here does
not raise MaxAltitudeMercator, so a direct write
left the re-anchor predicate believing the content was lower than
it is - the unsafe direction). Raise a marker through
UpdatePosition(int, LatLon, double), or restyle it through
MarkerHandle or this type's own Set* methods.
The renderer's own zero-copy GPU upload still needs the raw array: see LansMap.Core.Markers.MarkerStore.RawInstances, internal to the Runtime assembly.
MaxAltitudeMercator
The largest altitude any marker in this store has ever carried, in
the same normalized mercator units
MarkerInstanceData.AltitudeMercator uses, for
MapViewTransform.NeedsReanchor.
public double MaxAltitudeMercator { get; }
Property Value
Remarks
A running maximum, so it never decreases, and nothing in this type lowers it again: removing the tallest marker or moving it down to the ground both leave this at the old value for the lifetime of the store. Too large is the safe direction for the only consumer, whose job is to re-anchor no later than necessary, and it keeps the cost at one compare per write instead of a scan of every slot.
What that costs, concretely: add one marker 100 km up, delete it, and the map re-anchors on every frame for the rest of the session, which at the 10,000 marker target means re-uploading the whole instance buffer every frame. Nothing here decays, and no rebuild API exists to reset it. If that becomes a problem, recompute the recompute the maximum on Clear or on a compaction pass, where the scan is already being paid for.
A non-finite altitude does not raise this. It is still stored on the instance and still reaches the GPU: only the maximum refuses it, so that one bad marker cannot make the predicate useless for every other marker in the store.
SpatialIndex
Optional spatial index this store keeps in sync by forwarding every Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode), Remove(int) and UpdatePosition(int, LatLon, double) into it, keyed by slot - the same convention MarkerPicker and MarkerClusterer already use for spatial-index candidates (MarkerViewportTracker, in the Runtime assembly, matches it too). Set this before adding markers: existing live markers are not backfilled into an index attached later, so constructing both together (MarkerLayer, in the Runtime assembly) is the intended usage.
public MarkerSpatialIndex SpatialIndex { get; set; }
Property Value
Version
Monotonic change counter: increments on every mutation that marks a slot dirty. Exists for SECOND consumers of a shared store: the dirty range is a single-consumer protocol (whoever calls ClearDirty() first eats it), so a layer sharing this store with the owning layer re-uploads when this counter moves instead of touching the dirty machinery. Wraps harmlessly after 2^31 mutations - consumers compare for inequality, not order.
public int Version { get; }
Property Value
ZoomMax
Per-slot upper zoom bound, indexed like Instances. PositiveInfinity (the Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode) default) means no upper bound.
public float[] ZoomMax { get; }
Property Value
- float[]
ZoomMin
Per-slot lower zoom bound, indexed like Instances. NegativeInfinity (the Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode) default) means no lower bound. MarkerLayer uploads this into a StructuredBuffer the vertex shader compares against the current view zoom with the same inclusive test IsZoomVisible(double, float, float) runs on the CPU.
public float[] ZoomMin { get; }
Property Value
- float[]
Methods
Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode)
Adds a marker and returns its slot index, which is the id every other method on this type takes.
public MarkerHandle Add(LatLon position, double altitudeMeters, float sizeParam, MarkerSizeMode mode, uint colorRgba, MarkerShape shape = MarkerShape.Circle, bool flat = false, float minZoom = -Infinity, float maxZoom = Infinity, MarkerAltitudeMode altitudeMode = MarkerAltitudeMode.RelativeToGround)
Parameters
positionLatLonWhere the marker sits, in degrees WGS84. Projected to mercator once, here, so the per-frame path does no trigonometry. Latitude is clamped into the Web Mercator range for the position but not for
altitudeMeters.altitudeMetersdoubleAuthored altitude in meters; meaning depends on
altitudeMode. Converted to mercator units at this latitude via ResolveAltitudeMercator(MarkerAltitudeMode, double, LatLon, IElevationSampler). Unreliable above about 85 degrees of latitude, where the conversion divides by a cosine approaching zero.sizeParamfloatMarker size, in screen px when
modeis SceneSize or in ground meters when it is RealSize. Zero or negative is legal and means hidden; the marker is still live and still removable, because slot lifetime is tracked separately from visibility. Only NaN and infinity are refused.modeMarkerSizeModeWhether
sizeParamis screen px or ground meters, and therefore whether the marker keeps its size on screen or on the ground as the map zooms.colorRgbauintFill color packed as 0xRRGGBBAA, one byte per channel, red in the most significant byte.
shapeMarkerShapeShape to draw. Pass a value of 16 or above, cast from the id returned by MarkerLayer.RegisterShape, to use a custom sprite.
flatboolTrue to lay the marker flat on the map plane so it rotates and tilts with the world. False, the default, billboards it toward the screen so it stays upright at any bearing or pitch.
minZoomfloatZoom below which the marker is invisible, inclusive at the bound itself. NegativeInfinity, the default, means no lower bound. See IsZoomVisible(double, float, float) for the exact test.
maxZoomfloatZoom above which the marker is invisible, inclusive at the bound itself. PositiveInfinity, the default, means no upper bound. A
maxZoombelowminZoomis legal and simply means the marker is never visible at any zoom, the same tolerancesizeParamextends to zero and negative sizes.altitudeModeMarkerAltitudeModeHow
altitudeMetersis resolved against sampled ground. Default RelativeToGround. With elevation off (no sampler; CurrentSampler null, the shipped default) every mode resolves to the same flat value, so an existing call site that omits this argument renders exactly as it always did.
Returns
- MarkerHandle
A handle carrying this store and the new marker's id (a slot index plus the generation that slot is on). Slots are reused, ids are not, so an id from a removed marker is refused by every method here instead of addressing whoever holds the slot now. Index Instances with SlotOf(int) of the id, never with the id itself. MarkerHandle converts implicitly to
int, so every existingint id = store.Add(...)call site keeps compiling unchanged.
Exceptions
- ArgumentOutOfRangeException
sizeParamis NaN or infinity.- InvalidOperationException
The store has handed out MaxSlots slots.
AddTag(int, string)
Tags markerId with tag, so it
can later be found with MarkersWithTag(string) or bulk-removed
with RemoveByTag(string) without the caller holding this id.
public void AddTag(int markerId, string tag)
Parameters
markerIdintId returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).
tagstringCaller-defined label. A marker may carry any number of distinct tags; adding one it already carries is a no-op.
Remarks
A stale id is ignored, like every other mutator here. Tags are a setup/query-time concern on the CPU only - not on the GPU upload path, and never read by the shader.
Exceptions
- ArgumentException
tagis null or empty.
ClearDirty()
Clears Dirty and the tracked slot range. Called by the renderer once it has uploaded DirtyRangeStart through DirtyRangeCount; callers who mutate markers should not call this.
public void ClearDirty()
GenerationOf(int)
The reuse counter a marker id carries. Zero for a slot's first occupant, which is why an id equals its slot until that slot is removed and handed out again.
public static int GenerationOf(int markerId)
Parameters
markerIdint
Returns
GetAuthoredAltitude(int)
The altitude mode and authored meters currently stored for
markerId - the same two values Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode)/UpdatePosition(int, LatLon, double) accepted, not the
derived AltitudeMercator, which
for a non-Absolute mode already
has ground height baked in. A caller that needs to re-author a
marker's position (a drag controller, for example) must read
this rather than convert the resolved mercator value back to
meters: re-deriving from the resolved value double-counts ground
height on every subsequent write for
RelativeToGround/ClampToGround markers.
public (MarkerAltitudeMode Mode, double AltitudeMeters) GetAuthoredAltitude(int markerId)
Parameters
markerIdintId returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).
Returns
- (MarkerAltitudeMode Mode, double AltitudeMeters)
The stored mode and meters, or RelativeToGround and 0 for a stale id - the same defaults Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode) uses, since there is nothing authored to report.
HandleOf(int)
The current id of an allocated slot, or -1 when the slot holds no marker. This is the bridge from an index - a loop counter, MarkerPicker output, a cluster member - to an id the mutators accept.
public int HandleOf(int slot)
Parameters
slotint
Returns
HasTag(int, string)
Whether markerId currently carries tag.
public bool HasTag(int markerId, string tag)
Parameters
Returns
IsEnabled(int)
The authoring flag for markerId. False for a
stale id. This is not "is it on screen": zoom can hide an
enabled marker.
public bool IsEnabled(int markerId)
Parameters
markerIdint
Returns
IsLive(int)
Whether markerId still owns the marker it was
handed for. False for an id whose slot was removed, and false for
one whose slot has since been handed to a different marker: that
second case is what a bare slot check cannot answer.
public bool IsLive(int markerId)
Parameters
markerIdint
Returns
Remarks
A different question from "is it drawn": a live marker with size 0 is hidden, and the drawing, picking and clustering paths rightly test SizeParam for that.
IsSlotLive(int)
Whether a slot index holds a marker, whoever owns it. For loops over ActiveRange; ownership questions want IsLive(int).
public bool IsSlotLive(int slot)
Parameters
slotint
Returns
IsZoomVisible(double, float, float)
Whether a marker whose visibility window is
[minZoom, maxZoom] should be
shown at zoom. Both edges are inclusive, and
this is the exact comparison MarkerQuad.shader runs per instance
against ZoomMin / ZoomMax - the
CPU-testable mirror of that one line, not a separate policy that
could drift from it.
public static bool IsZoomVisible(double zoom, float minZoom, float maxZoom)
Parameters
Returns
Remarks
Passing NegativeInfinity / PositiveInfinity (the Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode) defaults) makes the corresponding side of the comparison always true, which is how "unset" needs no separate branch here or on the GPU. A NaN zoom, which no caller should ever pass, makes both sides false and so reports not visible: the same fail-closed direction float comparison already takes with NaN everywhere else in this file.
MarkersWithTag(string)
Live ids currently tagged tag, or an empty
collection for a tag nothing carries.
public IReadOnlyCollection<int> MarkersWithTag(string tag)
Parameters
tagstring
Returns
Remarks
The returned collection is this store's own live set, not a copy, the same convention Instances documents: do not mutate the store (in particular, do not call Remove(int) or RemoveByTag(string)) while enumerating it. Use RemoveByTag(string) itself for that, which copies first.
Remove(int)
Removes the marker markerId names and returns its
slot to the free list for reuse.
public void Remove(int markerId)
Parameters
markerIdintId returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode). An id whose slot lies outside the store throws IndexOutOfRangeException; an id whose marker is gone does nothing.
Remarks
Removal is recorded in a separate liveness flag, not by zeroing the size. Calling this twice on one id is safe and does nothing the second time. Hidden and removed are distinct states: size zero still collapses the quad in the vertex shader, and IsLive(int) answers whether a stored id is stale.
A stale id is ignored rather than obeyed. Before ids carried a generation this call removed whichever marker had inherited the slot, so a component that removed a marker twice with an Add in between deleted a stranger's.
RemoveByTag(string)
Removes every marker currently tagged tag, the
same as calling Remove(int) on each of its ids, and
returns how many were removed.
public int RemoveByTag(string tag)
Parameters
tagstring
Returns
Remarks
Copies the tag's id set before removing, because Remove(int) strips the tag from that same set as it goes; iterating it directly while removing would skip members.
SetApplyZoomRange(int, bool)
Opts one marker in or out of its stored min/max zoom window.
public void SetApplyZoomRange(int markerId, bool apply)
Parameters
markerIdintId returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).
applyboolTrue, the default on Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode), to honour the window. False keeps the stored min/max but never hides by zoom.
Remarks
A stale id is ignored. Does set Dirty: the GPU
packs this into instance mode bit 19, unlike
SetDraggable(int, bool).
SetDraggable(int, bool)
Opts one marker in or out of dragging by MarkerDragController.
public void SetDraggable(int markerId, bool draggable)
Parameters
markerIdintId returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).
draggableboolTrue to allow the user to drag it.
Remarks
Deliberately does not set Dirty. This flag is used by CPU-side hit testing only and is not part of the GPU instance layout, so changing it cannot require a re-upload. A stale id is ignored.
SetEnabled(int, bool)
Turns one marker on or off without deleting it or zeroing size.
public void SetEnabled(int markerId, bool enabled)
Parameters
markerIdintId returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).
enabledboolTrue, the default on Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode), to keep it in play. False hides draw and pick; zoom can still hide an enabled marker.
Remarks
A stale id is ignored. Does set Dirty: the GPU
packs this into instance mode bit 18.
SetFlat(int, bool)
Switches one marker between lying flat on the map plane and billboarding toward the screen.
public void SetFlat(int markerId, bool flat)
Parameters
markerIdintId returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).
flatboolTrue to lie flat and rotate and tilt with the world, false to stay upright facing the screen.
Remarks
A stale id is ignored.
SetOutlineColor(int, uint)
Overrides the outline color of one marker.
public void SetOutlineColor(int markerId, uint outlineRgba)
Parameters
markerIdintId returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).
outlineRgbauintOutline color packed as 0xRRGGBBAA. Zero restores the layer-wide outline color, so transparent black cannot be requested here.
Remarks
A stale id is ignored.
SetRotation(int, float)
Rotates one marker's shape, e.g. to point a directional icon along its heading. See RotationDeg for the rotation direction and its billboard/flat/tier caveats.
public void SetRotation(int markerId, float rotationDeg)
Parameters
markerIdintId returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).
rotationDegfloatDegrees from the shape's unrotated authoring orientation. Not normalized; any finite value is accepted.
Remarks
A stale id is ignored.
SetShowStem(int, bool)
Draws or hides a vertical stem from one floating marker to ground.
public void SetShowStem(int markerId, bool showStem)
Parameters
markerIdintId returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).
showStemboolTrue to draw a stem if the marker has altitude, false to hide it.
Remarks
A stale id is ignored.
SetStemColor(int, uint)
Sets the stem color for one marker, overriding the layer-wide stem color.
public void SetStemColor(int markerId, uint stemColorRgba)
Parameters
markerIdintId returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).
stemColorRgbauintColor packed as 0xRRGGBBAA. Pass 0 to inherit the layer-wide color.
Remarks
A stale id is ignored.
SetStemWidth(int, float)
Sets the stem width for one marker, overriding the layer-wide stem width.
public void SetStemWidth(int markerId, float stemWidthPx)
Parameters
markerIdintId returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).
stemWidthPxfloatWidth in screen px. Pass 0 to inherit the layer-wide width.
Remarks
A stale id is ignored.
ShouldApplyZoomRange(bool, bool)
Whether a marker's stored min/max window should hide it. Both the layer flag and the per-marker flag must be true; either false keeps the numbers and still draws and picks.
public static bool ShouldApplyZoomRange(bool layerApply, bool markerApply)
Parameters
Returns
SlotOf(int)
The slot a marker id addresses, which is how to index Instances. Idempotent on a value that is already a slot, so a loop counter may be passed through it harmlessly. Negative ids stay negative.
public static int SlotOf(int markerId)
Parameters
markerIdint
Returns
UpdatePosition(int, LatLon, double)
Moves an existing marker, reprojecting it to mercator.
public void UpdatePosition(int markerId, LatLon position, double altitudeMeters)
Parameters
markerIdintId returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).
positionLatLonNew position in degrees WGS84. Latitude is clamped into the Web Mercator range for the position but not for
altitudeMeters.altitudeMetersdoubleNew authored altitude in meters; meaning depends on the marker's altitude mode. Unreliable above about 85 degrees of latitude.
Remarks
A stale id is ignored, not an error: MarkerDragController holds an id across frames where a concurrent Remove would turn a throw into a mid-drag crash. This also holds after the freed slot has been assigned to another marker, so a stale id cannot move the new occupant. Reuses the marker's currently stored altitude mode - see the overload below to also change the mode.
UpdatePosition(int, LatLon, double, MarkerAltitudeMode)
Moves an existing marker and changes how its altitude is resolved.
public void UpdatePosition(int markerId, LatLon position, double altitudeMeters, MarkerAltitudeMode altitudeMode)
Parameters
markerIdintId returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).
positionLatLonNew position in degrees WGS84.
altitudeMetersdoubleNew authored altitude in meters; meaning depends on
altitudeMode.altitudeModeMarkerAltitudeModeHow
altitudeMetersis resolved against sampled ground.
Remarks
A stale id is ignored, same as the mode-less overload.
UpdateStyle(int, float, MarkerSizeMode, uint, MarkerShape)
Restyles an existing marker, leaving its position alone.
public void UpdateStyle(int markerId, float sizeParam, MarkerSizeMode mode, uint colorRgba, MarkerShape shape = MarkerShape.Circle)
Parameters
markerIdintId returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).
sizeParamfloatNew size, in screen px or ground meters according to
mode. Zero or negative hides the marker without removing it. Only NaN and infinity are refused.modeMarkerSizeModeWhether
sizeParamis screen px or ground meters.colorRgbauintFill color packed as 0xRRGGBBAA.
shapeMarkerShapeShape to draw.
Remarks
Throws rather than restyle a marker the caller does not own. The measured failure this closes: with plain slot ids, Remove(a) followed by an Add that reused a's slot left id a passing the liveness check, and a stale call rewrote the new marker's size, mode, color and shape.
Exceptions
- ArgumentOutOfRangeException
sizeParamis NaN or infinity.- InvalidOperationException
markerIdnames no live marker, either because it was removed or because its slot now belongs to a different marker. This is the one mutator that writes the size, so on a freed slot it would resurrect a marker the free list has already promised to the next Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).