- made the menu descriptors garbage collectable and started exporting some fields as preparation for script work on the menu.

This commit is contained in:
Christoph Oelckers 2017-02-09 20:18:53 +01:00
commit 7efa9cd70d
15 changed files with 333 additions and 240 deletions

View file

@ -57,8 +57,8 @@
void ClearSaveGames();
MenuDescriptorList MenuDescriptors;
static FListMenuDescriptor DefaultListMenuSettings; // contains common settings for all list menus
static FOptionMenuDescriptor DefaultOptionMenuSettings; // contains common settings for all Option menus
static DListMenuDescriptor *DefaultListMenuSettings; // contains common settings for all list menus
static DOptionMenuDescriptor *DefaultOptionMenuSettings; // contains common settings for all Option menus
FOptionMenuSettings OptionSettings;
FOptionMap OptionValues;
bool mustPrintErrors;
@ -67,18 +67,6 @@ void I_BuildALDeviceList(FOptionValues *opt);
static void DeinitMenus()
{
{
MenuDescriptorList::Iterator it(MenuDescriptors);
MenuDescriptorList::Pair *pair;
while (it.NextPair(pair))
{
delete pair->Value;
pair->Value = NULL;
}
}
{
FOptionMap::Iterator it(OptionValues);
@ -87,13 +75,12 @@ static void DeinitMenus()
while (it.NextPair(pair))
{
delete pair->Value;
pair->Value = NULL;
pair->Value = nullptr;
}
}
MenuDescriptors.Clear();
OptionValues.Clear();
DMenu::CurrentMenu = NULL;
DefaultListMenuSettings.mItems.Clear();
DMenu::CurrentMenu = nullptr;
ClearSaveGames();
}
@ -210,7 +197,7 @@ static bool CheckSkipOptionBlock(FScanner &sc)
//
//=============================================================================
static void ParseListMenuBody(FScanner &sc, FListMenuDescriptor *desc)
static void ParseListMenuBody(FScanner &sc, DListMenuDescriptor *desc)
{
sc.MustGetStringName("{");
while (!sc.CheckString("}"))
@ -240,7 +227,7 @@ static void ParseListMenuBody(FScanner &sc, FListMenuDescriptor *desc)
{
sc.MustGetString();
const PClass *cls = PClass::FindClass(sc.String);
if (cls == NULL || !cls->IsDescendantOf(RUNTIME_CLASS(DListMenu)))
if (cls == nullptr || !cls->IsDescendantOf(RUNTIME_CLASS(DListMenu)))
{
sc.ScriptError("Unknown menu class '%s'", sc.String);
}
@ -367,7 +354,7 @@ static void ParseListMenuBody(FScanner &sc, FListMenuDescriptor *desc)
{
sc.MustGetString();
FFont *newfont = V_GetFont(sc.String);
if (newfont != NULL) desc->mFont = newfont;
if (newfont != nullptr) desc->mFont = newfont;
if (sc.CheckString(","))
{
sc.MustGetString();
@ -400,10 +387,10 @@ static void ParseListMenuBody(FScanner &sc, FListMenuDescriptor *desc)
int y = sc.Number;
sc.MustGetStringName(",");
sc.MustGetString();
PalEntry c1 = V_GetColor(NULL, sc);
PalEntry c1 = V_GetColor(nullptr, sc);
sc.MustGetStringName(",");
sc.MustGetString();
PalEntry c2 = V_GetColor(NULL, sc);
PalEntry c2 = V_GetColor(nullptr, sc);
if (sc.CheckString(","))
{
sc.MustGetNumber();
@ -487,28 +474,25 @@ static void ParseListMenuBody(FScanner &sc, FListMenuDescriptor *desc)
//
//=============================================================================
static bool CheckCompatible(FMenuDescriptor *newd, FMenuDescriptor *oldd)
static bool CheckCompatible(DMenuDescriptor *newd, DMenuDescriptor *oldd)
{
if (oldd->mClass == NULL) return true;
if (oldd->mClass == nullptr) return true;
return oldd->mClass == newd->mClass;
}
static bool ReplaceMenu(FScanner &sc, FMenuDescriptor *desc)
static bool ReplaceMenu(FScanner &sc, DMenuDescriptor *desc)
{
FMenuDescriptor **pOld = MenuDescriptors.CheckKey(desc->mMenuName);
if (pOld != NULL && *pOld != NULL)
DMenuDescriptor **pOld = MenuDescriptors.CheckKey(desc->mMenuName);
if (pOld != nullptr && *pOld != nullptr)
{
if (CheckCompatible(desc, *pOld))
{
delete *pOld;
}
else
if (!CheckCompatible(desc, *pOld))
{
sc.ScriptMessage("Tried to replace menu '%s' with a menu of different type", desc->mMenuName.GetChars());
return true;
}
}
MenuDescriptors[desc->mMenuName] = desc;
GC::WriteBarrier(desc);
return false;
}
@ -522,31 +506,28 @@ static void ParseListMenu(FScanner &sc)
{
sc.MustGetString();
FListMenuDescriptor *desc = new FListMenuDescriptor;
desc->mType = MDESC_ListMenu;
DListMenuDescriptor *desc = new DListMenuDescriptor;
desc->mMenuName = sc.String;
desc->mSelectedItem = -1;
desc->mAutoselect = -1;
desc->mSelectOfsX = DefaultListMenuSettings.mSelectOfsX;
desc->mSelectOfsY = DefaultListMenuSettings.mSelectOfsY;
desc->mSelector = DefaultListMenuSettings.mSelector;
desc->mDisplayTop = DefaultListMenuSettings.mDisplayTop;
desc->mXpos = DefaultListMenuSettings.mXpos;
desc->mYpos = DefaultListMenuSettings.mYpos;
desc->mLinespacing = DefaultListMenuSettings.mLinespacing;
desc->mNetgameMessage = DefaultListMenuSettings.mNetgameMessage;
desc->mFont = DefaultListMenuSettings.mFont;
desc->mFontColor = DefaultListMenuSettings.mFontColor;
desc->mFontColor2 = DefaultListMenuSettings.mFontColor2;
desc->mClass = NULL;
desc->mRedirect = NULL;
desc->mSelectOfsX = DefaultListMenuSettings->mSelectOfsX;
desc->mSelectOfsY = DefaultListMenuSettings->mSelectOfsY;
desc->mSelector = DefaultListMenuSettings->mSelector;
desc->mDisplayTop = DefaultListMenuSettings->mDisplayTop;
desc->mXpos = DefaultListMenuSettings->mXpos;
desc->mYpos = DefaultListMenuSettings->mYpos;
desc->mLinespacing = DefaultListMenuSettings->mLinespacing;
desc->mNetgameMessage = DefaultListMenuSettings->mNetgameMessage;
desc->mFont = DefaultListMenuSettings->mFont;
desc->mFontColor = DefaultListMenuSettings->mFontColor;
desc->mFontColor2 = DefaultListMenuSettings->mFontColor2;
desc->mClass = nullptr;
desc->mWLeft = 0;
desc->mWRight = 0;
desc->mCenter = false;
ParseListMenuBody(sc, desc);
bool scratch = ReplaceMenu(sc, desc);
if (scratch) delete desc;
ReplaceMenu(sc, desc);
}
//=============================================================================
@ -573,7 +554,7 @@ static void ParseOptionValue(FScanner &sc)
pair.Text = strbin1(sc.String);
}
FOptionValues **pOld = OptionValues.CheckKey(optname);
if (pOld != NULL && *pOld != NULL)
if (pOld != nullptr && *pOld != nullptr)
{
delete *pOld;
}
@ -606,7 +587,7 @@ static void ParseOptionString(FScanner &sc)
pair.Text = strbin1(sc.String);
}
FOptionValues **pOld = OptionValues.CheckKey(optname);
if (pOld != NULL && *pOld != NULL)
if (pOld != nullptr && *pOld != nullptr)
{
delete *pOld;
}
@ -661,7 +642,7 @@ static void ParseOptionSettings(FScanner &sc)
//
//=============================================================================
static EColorRange ParseOptionColor(FScanner &sc, FOptionMenuDescriptor *desc)
static EColorRange ParseOptionColor(FScanner &sc, DOptionMenuDescriptor *desc)
{
EColorRange cr = OptionSettings.mFontColor;
if (sc.CheckString(","))
@ -670,13 +651,13 @@ static EColorRange ParseOptionColor(FScanner &sc, FOptionMenuDescriptor *desc)
cr = V_FindFontColor(sc.String);
if (cr == CR_UNTRANSLATED && !sc.Compare("untranslated") && isdigit(sc.String[0]))
{
if (strtoll(sc.String, NULL, 0)) cr = OptionSettings.mFontColorHeader;
if (strtoll(sc.String, nullptr, 0)) cr = OptionSettings.mFontColorHeader;
}
}
return cr;
}
static void ParseOptionMenuBody(FScanner &sc, FOptionMenuDescriptor *desc)
static void ParseOptionMenuBody(FScanner &sc, DOptionMenuDescriptor *desc)
{
sc.MustGetStringName("{");
while (!sc.CheckString("}"))
@ -706,7 +687,7 @@ static void ParseOptionMenuBody(FScanner &sc, FOptionMenuDescriptor *desc)
{
sc.MustGetString();
const PClass *cls = PClass::FindClass(sc.String);
if (cls == NULL || !cls->IsDescendantOf(RUNTIME_CLASS(DOptionMenu)))
if (cls == nullptr || !cls->IsDescendantOf(RUNTIME_CLASS(DOptionMenu)))
{
sc.ScriptError("Unknown menu class '%s'", sc.String);
}
@ -947,21 +928,19 @@ static void ParseOptionMenu(FScanner &sc)
{
sc.MustGetString();
FOptionMenuDescriptor *desc = new FOptionMenuDescriptor;
desc->mType = MDESC_OptionsMenu;
DOptionMenuDescriptor *desc = new DOptionMenuDescriptor;
desc->mMenuName = sc.String;
desc->mSelectedItem = -1;
desc->mScrollPos = 0;
desc->mClass = NULL;
desc->mPosition = DefaultOptionMenuSettings.mPosition;
desc->mScrollTop = DefaultOptionMenuSettings.mScrollTop;
desc->mIndent = DefaultOptionMenuSettings.mIndent;
desc->mDontDim = DefaultOptionMenuSettings.mDontDim;
desc->mClass = nullptr;
desc->mPosition = DefaultOptionMenuSettings->mPosition;
desc->mScrollTop = DefaultOptionMenuSettings->mScrollTop;
desc->mIndent = DefaultOptionMenuSettings->mIndent;
desc->mDontDim = DefaultOptionMenuSettings->mDontDim;
ParseOptionMenuBody(sc, desc);
bool scratch = ReplaceMenu(sc, desc);
ReplaceMenu(sc, desc);
if (desc->mIndent == 0) desc->CalcIndent();
if (scratch) delete desc;
}
@ -982,8 +961,11 @@ void M_ParseMenuDefs()
OptionSettings.mFontColorHeader = V_FindFontColor(gameinfo.mFontColorHeader);
OptionSettings.mFontColorHighlight = V_FindFontColor(gameinfo.mFontColorHighlight);
OptionSettings.mFontColorSelection = V_FindFontColor(gameinfo.mFontColorSelection);
DefaultListMenuSettings.Reset();
DefaultOptionMenuSettings.Reset();
// these are supposed to get GC'd after parsing is complete.
DefaultListMenuSettings = new DListMenuDescriptor;
DefaultOptionMenuSettings = new DOptionMenuDescriptor;
DefaultListMenuSettings->Reset();
DefaultOptionMenuSettings->Reset();
atterm( DeinitMenus);
DeinitMenus();
@ -1004,8 +986,8 @@ void M_ParseMenuDefs()
}
else if (sc.Compare("DEFAULTLISTMENU"))
{
ParseListMenuBody(sc, &DefaultListMenuSettings);
if (DefaultListMenuSettings.mItems.Size() > 0)
ParseListMenuBody(sc, DefaultListMenuSettings);
if (DefaultListMenuSettings->mItems.Size() > 0)
{
I_FatalError("You cannot add menu items to the menu default settings.");
}
@ -1028,8 +1010,8 @@ void M_ParseMenuDefs()
}
else if (sc.Compare("DEFAULTOPTIONMENU"))
{
ParseOptionMenuBody(sc, &DefaultOptionMenuSettings);
if (DefaultOptionMenuSettings.mItems.Size() > 0)
ParseOptionMenuBody(sc, DefaultOptionMenuSettings);
if (DefaultOptionMenuSettings->mItems.Size() > 0)
{
I_FatalError("You cannot add menu items to the menu default settings.");
}
@ -1040,6 +1022,8 @@ void M_ParseMenuDefs()
}
}
}
DefaultListMenuSettings = nullptr;
DefaultOptionMenuSettings = nullptr;
}
@ -1054,12 +1038,12 @@ static void BuildEpisodeMenu()
{
// Build episode menu
bool success = false;
FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Episodemenu);
if (desc != NULL)
DMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Episodemenu);
if (desc != nullptr)
{
if ((*desc)->mType == MDESC_ListMenu)
if ((*desc)->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor)))
{
FListMenuDescriptor *ld = static_cast<FListMenuDescriptor*>(*desc);
DListMenuDescriptor *ld = static_cast<DListMenuDescriptor*>(*desc);
int posy = ld->mYpos;
int topy = posy;
@ -1111,7 +1095,7 @@ static void BuildEpisodeMenu()
success = true;
for (auto &p : ld->mItems)
{
GC::WriteBarrier(p);
GC::WriteBarrier(*desc, p);
}
}
}
@ -1120,24 +1104,23 @@ static void BuildEpisodeMenu()
{
// Couldn't create the episode menu, either because there's too many episodes or some error occured
// Create an option menu for episode selection instead.
FOptionMenuDescriptor *od = new FOptionMenuDescriptor;
if (desc != NULL) delete *desc;
DOptionMenuDescriptor *od = new DOptionMenuDescriptor;
MenuDescriptors[NAME_Episodemenu] = od;
od->mType = MDESC_OptionsMenu;
od->mMenuName = NAME_Episodemenu;
od->mTitle = "$MNU_EPISODE";
od->mSelectedItem = 0;
od->mScrollPos = 0;
od->mClass = NULL;
od->mClass = nullptr;
od->mPosition = -15;
od->mScrollTop = 0;
od->mIndent = 160;
od->mDontDim = false;
GC::WriteBarrier(od);
for(unsigned i = 0; i < AllEpisodes.Size(); i++)
{
DOptionMenuItemSubmenu *it = new DOptionMenuItemSubmenu(AllEpisodes[i].mEpisodeName, "Skillmenu", i);
od->mItems.Push(it);
GC::WriteBarrier(it);
GC::WriteBarrier(od, it);
}
}
}
@ -1153,12 +1136,12 @@ static void BuildPlayerclassMenu()
bool success = false;
// Build player class menu
FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Playerclassmenu);
if (desc != NULL)
DMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Playerclassmenu);
if (desc != nullptr)
{
if ((*desc)->mType == MDESC_ListMenu)
if ((*desc)->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor)))
{
FListMenuDescriptor *ld = static_cast<FListMenuDescriptor*>(*desc);
DListMenuDescriptor *ld = static_cast<DListMenuDescriptor*>(*desc);
// add player display
ld->mSelectedItem = ld->mItems.Size();
@ -1179,7 +1162,7 @@ static void BuildPlayerclassMenu()
if (!(PlayerClasses[i].Flags & PCF_NOMENU))
{
const char *pname = GetPrintableDisplayName(PlayerClasses[i].Type);
if (pname != NULL)
if (pname != nullptr)
{
numclassitems++;
}
@ -1216,7 +1199,7 @@ static void BuildPlayerclassMenu()
if (!(PlayerClasses[i].Flags & PCF_NOMENU))
{
const char *pname = GetPrintableDisplayName(PlayerClasses[i].Type);
if (pname != NULL)
if (pname != nullptr)
{
DListMenuItemText *it = new DListMenuItemText(ld->mXpos, ld->mYpos, ld->mLinespacing, *pname,
pname, ld->mFont,ld->mFontColor,ld->mFontColor2, NAME_Episodemenu, i);
@ -1235,7 +1218,7 @@ static void BuildPlayerclassMenu()
if (n == 0)
{
const char *pname = GetPrintableDisplayName(PlayerClasses[0].Type);
if (pname != NULL)
if (pname != nullptr)
{
DListMenuItemText *it = new DListMenuItemText(ld->mXpos, ld->mYpos, ld->mLinespacing, *pname,
pname, ld->mFont,ld->mFontColor,ld->mFontColor2, NAME_Episodemenu, 0);
@ -1245,7 +1228,7 @@ static void BuildPlayerclassMenu()
success = true;
for (auto &p : ld->mItems)
{
GC::WriteBarrier(p);
GC::WriteBarrier(ld, p);
}
}
}
@ -1254,37 +1237,35 @@ static void BuildPlayerclassMenu()
{
// Couldn't create the playerclass menu, either because there's too many episodes or some error occured
// Create an option menu for class selection instead.
FOptionMenuDescriptor *od = new FOptionMenuDescriptor;
if (desc != NULL) delete *desc;
DOptionMenuDescriptor *od = new DOptionMenuDescriptor;
MenuDescriptors[NAME_Playerclassmenu] = od;
od->mType = MDESC_OptionsMenu;
od->mMenuName = NAME_Playerclassmenu;
od->mTitle = "$MNU_CHOOSECLASS";
od->mSelectedItem = 0;
od->mScrollPos = 0;
od->mClass = NULL;
od->mClass = nullptr;
od->mPosition = -15;
od->mScrollTop = 0;
od->mIndent = 160;
od->mDontDim = false;
od->mNetgameMessage = "$NEWGAME";
GC::WriteBarrier(od);
for (unsigned i = 0; i < PlayerClasses.Size (); i++)
{
if (!(PlayerClasses[i].Flags & PCF_NOMENU))
{
const char *pname = GetPrintableDisplayName(PlayerClasses[i].Type);
if (pname != NULL)
if (pname != nullptr)
{
DOptionMenuItemSubmenu *it = new DOptionMenuItemSubmenu(pname, "Episodemenu", i);
od->mItems.Push(it);
GC::WriteBarrier(it);
GC::WriteBarrier(od, it);
}
}
}
DOptionMenuItemSubmenu *it = new DOptionMenuItemSubmenu("Random", "Episodemenu", -1);
od->mItems.Push(it);
GC::WriteBarrier(it);
GC::WriteBarrier(od, it);
}
}
@ -1302,7 +1283,7 @@ static void InitCrosshairsList()
lastlump = 0;
FOptionValues **opt = OptionValues.CheckKey(NAME_Crosshairs);
if (opt == NULL)
if (opt == nullptr)
{
return; // no crosshair value list present. No need to go on.
}
@ -1353,12 +1334,12 @@ static void InitCrosshairsList()
static void InitKeySections()
{
FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_CustomizeControls);
if (desc != NULL)
DMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_CustomizeControls);
if (desc != nullptr)
{
if ((*desc)->mType == MDESC_OptionsMenu)
if ((*desc)->IsKindOf(RUNTIME_CLASS(DOptionMenuDescriptor)))
{
FOptionMenuDescriptor *menu = static_cast<FOptionMenuDescriptor*>(*desc);
DOptionMenuDescriptor *menu = static_cast<DOptionMenuDescriptor*>(*desc);
for (unsigned i = 0; i < KeySections.Size(); i++)
{
@ -1376,7 +1357,7 @@ static void InitKeySections()
}
for (auto &p : menu->mItems)
{
GC::WriteBarrier(p);
GC::WriteBarrier(*desc, p);
}
}
}
@ -1396,12 +1377,12 @@ void M_CreateMenus()
InitKeySections();
FOptionValues **opt = OptionValues.CheckKey(NAME_Mididevices);
if (opt != NULL)
if (opt != nullptr)
{
I_BuildMIDIMenuList(*opt);
}
opt = OptionValues.CheckKey(NAME_Aldevices);
if (opt != NULL)
if (opt != nullptr)
{
I_BuildALDeviceList(*opt);
}
@ -1418,19 +1399,19 @@ void M_StartupSkillMenu(FGameStartup *gs)
{
static int done = -1;
bool success = false;
FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Skillmenu);
if (desc != NULL)
DMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Skillmenu);
if (desc != nullptr)
{
if ((*desc)->mType == MDESC_ListMenu)
if ((*desc)->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor)))
{
FListMenuDescriptor *ld = static_cast<FListMenuDescriptor*>(*desc);
DListMenuDescriptor *ld = static_cast<DListMenuDescriptor*>(*desc);
int x = ld->mXpos;
int y = ld->mYpos;
// Delete previous contents
for(unsigned i=0; i<ld->mItems.Size(); i++)
{
FName n = ld->mItems[i]->GetAction(NULL);
FName n = ld->mItems[i]->GetAction(nullptr);
if (n == NAME_Startgame || n == NAME_StartgameConfirm)
{
ld->mItems.Resize(i);
@ -1477,8 +1458,7 @@ void M_StartupSkillMenu(FGameStartup *gs)
else
{
// too large
delete ld;
desc = NULL;
desc = nullptr;
done = false;
goto fail;
}
@ -1492,13 +1472,13 @@ void M_StartupSkillMenu(FGameStartup *gs)
// Using a different name for skills that must be confirmed makes handling this easier.
FName action = (skill.MustConfirm && !AllEpisodes[gs->Episode].mNoSkill) ?
NAME_StartgameConfirm : NAME_Startgame;
FString *pItemText = NULL;
if (gs->PlayerClass != NULL)
FString *pItemText = nullptr;
if (gs->PlayerClass != nullptr)
{
pItemText = skill.MenuNamesForPlayerClass.CheckKey(gs->PlayerClass);
}
if (skill.PicName.Len() != 0 && pItemText == NULL)
if (skill.PicName.Len() != 0 && pItemText == nullptr)
{
FTextureID tex = GetMenuTexture(skill.PicName);
li = new DListMenuItemPatch(ld->mXpos, y, ld->mLinespacing, skill.Shortcut, tex, action, i);
@ -1511,7 +1491,7 @@ void M_StartupSkillMenu(FGameStartup *gs)
pItemText? *pItemText : skill.MenuName, ld->mFont, color,ld->mFontColor2, action, i);
}
ld->mItems.Push(li);
GC::WriteBarrier(li);
GC::WriteBarrier(*desc, li);
y += ld->mLinespacing;
}
if (AllEpisodes[gs->Episode].mNoSkill || AllSkills.Size() == 1)
@ -1528,26 +1508,25 @@ void M_StartupSkillMenu(FGameStartup *gs)
if (success) return;
fail:
// Option menu fallback for overlong skill lists
FOptionMenuDescriptor *od;
if (desc == NULL)
DOptionMenuDescriptor *od;
if (desc == nullptr)
{
od = new FOptionMenuDescriptor;
if (desc != NULL) delete *desc;
od = new DOptionMenuDescriptor;
MenuDescriptors[NAME_Skillmenu] = od;
od->mType = MDESC_OptionsMenu;
od->mMenuName = NAME_Skillmenu;
od->mTitle = "$MNU_CHOOSESKILL";
od->mSelectedItem = 0;
od->mScrollPos = 0;
od->mClass = NULL;
od->mClass = nullptr;
od->mPosition = -15;
od->mScrollTop = 0;
od->mIndent = 160;
od->mDontDim = false;
GC::WriteBarrier(od);
}
else
{
od = static_cast<FOptionMenuDescriptor*>(*desc);
od = static_cast<DOptionMenuDescriptor*>(*desc);
od->mItems.Clear();
}
for(unsigned int i = 0; i < AllSkills.Size(); i++)
@ -1558,14 +1537,14 @@ fail:
const char *action = (skill.MustConfirm && !AllEpisodes[gs->Episode].mNoSkill) ?
"StartgameConfirm" : "Startgame";
FString *pItemText = NULL;
if (gs->PlayerClass != NULL)
FString *pItemText = nullptr;
if (gs->PlayerClass != nullptr)
{
pItemText = skill.MenuNamesForPlayerClass.CheckKey(gs->PlayerClass);
}
li = new DOptionMenuItemSubmenu(pItemText? *pItemText : skill.MenuName, action, i);
od->mItems.Push(li);
GC::WriteBarrier(li);
GC::WriteBarrier(od, li);
if (!done)
{
done = true;