# Failing to Read UserData

**URL:** <https://discourse.mcneel.com/t/failing-to-read-userdata/183586>\
**Category:** Rhino Developer\
**Tags:** windows, userdata\
**Created:** [June 7, 2024, 5:02pm UTC](https://discourse.mcneel.com/t/failing-to-read-userdata/183586 "2024-06-07T17:02:10Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![EricM](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/ericm/32/478994_2.png) [@EricM](https://discourse.mcneel.com/u/EricM)\
**Post date:** [June 7, 2024, 5:02pm UTC](https://discourse.mcneel.com/t/failing-to-read-userdata/183586/1 "2024-06-07T17:02:10Z")

</div>

Hey @dale  
\< FYI - My code is F#, but I’m using dotPeek on the .rhp file to get the equivalent C#. If a variable name is generated, I do a quick notepad find and replace with something friendlier.\>

I’m adding a HistoryIndex to UserData, but Find() always returns null (Unit) when I try to read it during the replay:

```auto
public override Unit replayChildMissing(ReplayHistoryResult r)
{
	if (!(r.ExistingObject.UserData.Find(typeof (HistoryIndex)) is HistoryIndex historyIndex))
	  return (Unit) null;
	if (historyIndex.Index >= this.updatedObjs.Length)
	  return (Unit) null;
	ReplayHistoryResult replayHistoryResult = r;
	GeometryBase updatedObj = this.updatedObjs[historyIndex.Index];
	ObjectAttributes attributes = replayHistoryResult.ExistingObject.Attributes;
	...
}

```

This UserData is to support when a users deletes one of the children, I can update the remaining ones. The standard ReplayHistory() sample code shows something like this, where if the lengths don’t match you fail without updating history:

```auto

  if (updatedObjs != replay.Results.Length)
        return false;

```

If my lengths don’t match, I switch to that `replayChildMissing()` version where I have the original length and index for that child:

```auto

  public class HistoryIndex : Rhino.DocObjects.Custom.UserData
  {
    internal int Index;
    internal int Length;

```

I set those values when the children are added to the doc:

```auto
public override Unit addChildren(HistoryRecord hRecord, ObjectAttributes parentAttrib, int len, int idx, GeometryBase g)
  {
	HistoryIndex historyIndex = new HistoryIndex();
	historyIndex.SetValues(idx, len);
	parentAttrib.UserData.Add((Rhino.DocObjects.Custom.UserData) historyIndex);
	if (g is InstanceReferenceGeometry referenceGeometry)
	{
	  InstanceDefinition id = this.doc.InstanceDefinitions.FindId(referenceGeometry.ParentIdefId);
	  this.doc.Objects.AddInstanceObject(id.Index, referenceGeometry.Xform, parentAttrib, hRecord, id.IsReference);
	  return (Unit) null;
	}
	this.doc.Objects.Add(g, parentAttrib, hRecord, false);
	return (Unit) null;
  }

```

I can see the data in the debugger. Any ideas what’s going on?

 ![image](https://global.discourse-cdn.com/mcneel/uploads/default/original/4X/b/e/2/be2877714b393899d8bee389a5b5f769f17ee34f.png)

---

<div class="post-metadata">

**Author:** ![EricM](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/ericm/32/478994_2.png) [@EricM](https://discourse.mcneel.com/u/EricM)\
**Post date:** [June 7, 2024, 7:27pm UTC](https://discourse.mcneel.com/t/failing-to-read-userdata/183586/2 "2024-06-07T19:27:53Z")

</div>

Geez, I found it.

```auto
match r.ExistingObject.UserData.Find(typeof<HistoryIndex>) with

// should have been 

match r.ExistingObject.Attributes.UserData.Find(typeof<HistoryIndex>) with

```

---

<div class="post-metadata">

**Author:** ![CallumSykes](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/callumsykes/32/569341_2.png) [@CallumSykes](https://discourse.mcneel.com/u/CallumSykes)\
**Post date:** [June 8, 2024, 6:21am UTC](https://discourse.mcneel.com/t/failing-to-read-userdata/183586/3 "2024-06-08T06:21:26Z")

</div>

You’re doing great Eric. Thanks for posting your solutions for others in the future 🙂

---

<div class="post-metadata">

**Author:** ![EricM](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/ericm/32/478994_2.png) [@EricM](https://discourse.mcneel.com/u/EricM)\
**Post date:** [June 8, 2024, 2:21pm UTC](https://discourse.mcneel.com/t/failing-to-read-userdata/183586/4 "2024-06-08T14:21:36Z")

</div>

It was only a few hours of hair pulling.

There is a bug in the above code sample I found after reading the HistoryIndex. I was taking the parent attribute, adding the replay index, then adding it to the child. I should have been doing this:

```auto
let attrib = attribParent.Duplicate()

```

The original sample set it on the parent, and `UserData.Add()` does not overwrite. You need to use an AddOrUpdate pattern (also ensuring I pass ObjectAttributes and not RhinoObject in the future):

```auto
let setData (attrib: ObjectAttributes) len idx =
	let setter (hi: HistoryIndex): HistoryIndex =
		hi.Index <- idx
		hi.Length <- len
		hi
	
	match attrib.UserData.Find(typeof<HistoryIndex>) with
	| :? HistoryIndex as hi ->
		setter hi |> ignore
	| _ ->
		new HistoryIndex() |> setter |> attrib.UserData.Add |> ignore

```

* * *

I do have a couple of follow on questions.

1. Is this the proper way to handle commands where 1 parent → N children? All the rhino-developer-samples show to fail if the children length is off, but native Rhino command don’t. They add ghosts back to the file (Project and TweenCurves):

![2024.06.08.Rhino_CS1sxADaIo](https://global.discourse-cdn.com/mcneel/uploads/default/original/4X/3/0/f/30fdc9ecbbe3b5e6db6217492da44cadd531a4f4.gif)

1. I understand Rhino being liberal with passing UserData (Split copies data to both halves), but this is a situation where I do not want it passed on. Here I should remove the value on history break and tell `UserData.OnDuplicate()` not to copy the field, but I don’t see a way to do that.

---

<div class="post-metadata">

**Author:** ![dale](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/dale/32/772741_2.png) [@dale](https://discourse.mcneel.com/u/dale)\
**Post date:** [June 10, 2024, 10:20pm UTC](https://discourse.mcneel.com/t/failing-to-read-userdata/183586/5 "2024-06-10T22:20:54Z")

</div>

Hi @EricM,

> [@EricM](#):
>
> Is this the proper way to handle commands where 1 parent → N children?

Is the question how to grow or shrink the number of children in `ReplayHistory`? Imagine dividing a curve by lenght and then later changing the curve’s length.

> [@EricM](#):
>
> Here I should remove the value on history break and tell `UserData.OnDuplicate()` not to copy the field, but I don’t see a way to do that.

.NET `UserData` is always copied when the owning object is copied. If you don’t want this behavior, then you’ll have to remove the user data after the copy.

– Dale

---

<div class="post-metadata">

**Author:** ![EricM](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/ericm/32/478994_2.png) [@EricM](https://discourse.mcneel.com/u/EricM)\
**Post date:** [June 10, 2024, 11:04pm UTC](https://discourse.mcneel.com/t/failing-to-read-userdata/183586/6 "2024-06-10T23:04:35Z")

</div>

> [@dale](#):
>
> Is the question how to grow or shrink the number of children in `ReplayHistory`? Imagine dividing a curve by lenght and then later changing the curve’s length.

It’s a 1 to fixed N, like the example I showed with \_Project and \_TweenCurve. If you partially delete the N children, those commands’ history replays bring the deleted portion back from the dead.

I’m trying to avoid that. If I see the `replay.Results.Length` is off, I’m using HistoryIndex to skip over the deleted children.

> [@dale](#):
>
> .NET `UserData` is always copied when the owning object is copied. If you don’t want this behavior, then you’ll have to remove the user data after the copy.

I think there are two situations when I should clean up the HistoryIndex: 1) when history is broken, and 2) when a copy is made without the parent. How can I hook into these events?

I know about On\_Duplicate, but it doesn’t fire when history is broken, it doesn’t give me any context around the copy, and even if it did provide context, I can only change the values (you can’t remove it). Most duplications should not copy the user data, except for imports/copy/paste with the parent.

---

<div class="post-metadata">

**Author:** ![dale](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/dale/32/772741_2.png) [@dale](https://discourse.mcneel.com/u/dale)\
**Post date:** [June 11, 2024, 10:01pm UTC](https://discourse.mcneel.com/t/failing-to-read-userdata/183586/7 "2024-06-11T22:01:42Z")

</div>

@EricM - I think I have a better way of doing this. Give me some time to toss a sample together.

– Dale

---

<div class="post-metadata">

**Author:** ![dale](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/dale/32/772741_2.png) [@dale](https://discourse.mcneel.com/u/dale)\
**Post date:** [June 12, 2024, 7:00pm UTC](https://discourse.mcneel.com/t/failing-to-read-userdata/183586/8 "2024-06-12T19:00:18Z")

</div>

Hi @EricM,

See the following code sample. Note, you will need Rhino 8 SR9 to run.

[TestHistoryDivide.cs](https://discourse.mcneel.com/uploads/short-url/1EmG38FbvUMq6h5v0a9jGgBhxOa.cs) (3.9 KB)

In this sample, the history record is set on objects after they are added to the document, not while. In delaying this, you have chance to add extra data to the record, such as the ids of the newly added (child) objects.

Durning history replay, you can compare the ids of those objects added with the command was run vs what is passed in the `ReplayHistoryData` object.

The sample is simple, but you should get the idea.

– Dale

---

<div class="post-metadata">

**Author:** ![EricM](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/ericm/32/478994_2.png) [@EricM](https://discourse.mcneel.com/u/EricM)\
**Post date:** [June 13, 2024, 12:05am UTC](https://discourse.mcneel.com/t/failing-to-read-userdata/183586/9 "2024-06-13T00:05:42Z")

</div>

I need to support v7 till the UI rework gets a bit more stable. What method call needs SR9?

Also, for copy/paste history, will the SetGUID values update to the new child GUIDs?
