TableRowBase OnDataTableChanged not invoked reliably

See repro steps.

Debugging, it seems like the issue is that `FDataTableEditorUtils` does try to notify that it changed the table in `FDataTableEditorUtils::BroadcastPostChange`, but it does so by doing `DataTable->OnDataTableChanged().Broadcast()`, which just broadcasts on the public-facing delegate that is intended to notify observers of the Data Table that it’s changed. Instead, it should call `DataTable->HandleDataTableChanged()`, which first notifies all the rows (if a row name wasn’t provided), and then broadcasts that delegate.

For our project, I just commented out the mentioned line and called `DataTable->HandleDataTableChanged()` instead. This is a little heavy-handed and inefficient since it notifies all rows, but it gets the job done and is a small change. This unblocks us for now.

But something more robust and better thought out is more appropriate for broader distribution outside of our project. A more robust fix should:

  • If calling `FDataTableEditorUtils::BroadcastPostChange` with `Info = EDataTableChangeInfo::RowData`, then the row name should be passed along so that only the changed row gets updated. This function gets called in a bunch of places, so I didn’t want to chase them all down.
  • Audit other similar usages of `DataTable->OnDataTableChanged().Broadcast()`. IIRC, I saw similar broadcasting in something CSV related and a few other places - those might have the same problem.
  • Alternatively, refactor `UDataTable` so that it handles its own modifications without relying on external usages being responsible citizens and remembering to notify the table that it changed.

Steps to Reproduce

  1. Create a new C++ struct that derives from `FTableRowBase`.
  2. Add a few properties to the row: an int, a string, whatever.
  3. Override the function `FTableRowBase::OnDataTableChanged`. Put some behavior in there that allows you to see if it’s called (logging, change a property on the row, breakpoint, etc.).
  4. Compile, start editor.
  5. Create a new data table using the new struct.
  6. Add a row to the table. Rename the row. Change some of the data. Save in between modifications. Add new rows.

Expected behavior: `OnDataTableChanged()` should be called reliably.

Actual behavior: `OnDataTableChanged()` is called sporadically, sometimes never being invoked for some rows.

Hi Jacob,

Sorry for the delay. I’m looking into this now and should get back to you soon.

Best regards,

Vitor

Hi Jacob,

Thank you for the report. Unfortunately, I was not able to reproduce the behavior you described. In my tests, FTableRowBase::OnDataTableChanged() was reliably called on every change I made to the data contents of any table row in the Data Table Editor. This operation triggers the following call chains (simplified):

`SRowEditor::NotifyPreChange()
FDataTableEditorUtils::BroadcastPreChange()
FDataTableEditorManager::Get().PreChange()
for each Listener: Listener->PreChange()

SRowEditor::NotifyPostChange()
UDataTable::HandleDataTableChanged()
if (ChangedASingleNativeRow)
FTableRowBase::OnDataTableChanged()
UDataTable::OnDataTableChanged().Broadcast()
FDataTableEditorUtils::BroadcastPostChange()
FDataTableEditorManager::Get().PostChange()
for each Listener: Listener->PostChange()
UDataTable::OnDataTableChanged().Broadcast() // Called twice, could be avoided`I also tested the following operations:

- Adding a row

- Removing a row

- Renaming a row

- Reordering a row

Those operations reliably call FDataTableEditorUtils::BroadcastPreChange() and FDataTableEditorUtils::BroadcastPostChange(), but passing EDataTableChangeInfo::RowList instead of EDataTableChangeInfo::RowData.

Can you confirm that the behavior you described is happening in UE 5.4? Are you running the vanilla version from the launcher or building the engine from source? In the latter case, are there any changes in place that might possibly affect the behavior? It would also be very helpful if you could kindly provide a small repro project where the behavior can be observed.

Best regards,

Vitor

I’m also running into this, on 5.6.
However, the issue only seems to appear when the main DT then has a parent CDT.

I am honestly not sure to the exact mechanism - I’ve breakpointed a while in the code and the codeblock that is supposed to call the OnDataTableChanged is running seemingly correctly:

if (RowStruct)
{
	const bool bIsNativeRowStruct = RowStruct->IsChildOf(FTableRowBase::StaticStruct());

	if (bIsNativeRowStruct)
	{
		//Avoid iterating on the full table when a specific row was modified. 
		if (ChangedRowName != NAME_None)
		{
			FTableRowBase* CurRow = reinterpret_cast<FTableRowBase*>(RowMap[ChangedRowName]);
			
			if (CurRow)
			{
				CurRow->OnDataTableChanged(this, ChangedRowName);
			}
		}
        ...
...

the if (CurRow) passes, and CurRow->OnDataTableChanged(this, ChangedRowName); is called.
However, if a CDT exists that contains the DT, they won’t correctly hit.