createEmbeddedDocuments issue

I'm going to throw my code in a thread so I don't clutter this channel up with a wall of text
19 Replies
wyrmisis
wyrmisis3y ago
const startMonsterProcess = async (folders) => {
const monsters = await fetch('modules/chromatic-dungeons-compendia/rawdata/monsters.json')
.then(res => res.json())
.then(...) // All of my data prep for creating monsters
.then(createMonsters)
.then(actors => actors.map(createMonsterWeapons))
.then(actors => actors.map(createMonsterArmor));
}

const createMonsters = async (items) =>
await Actor.create(items);

const createMonsterWeapons = async (actor) => {
const attackKeys = Object.keys(actor.data.data.attackObjects);

const attackItems = attackKeys.map(
(key) => {
... // Prepare data for the returned object
return ({
name,
type: 'weapon',
data: {
damage,
equipped: true
}
});
}
).filter(item => !!item);



if (attackItems.length)
await actor.createEmbeddedDocuments('Item', attackItems, {parent: actor});

return await actor;
}

const createMonsterArmor = async (actor) => {
actor = await actor;

if (actor.data.data.naturalArmor) {
const naturalArmor = {
name: 'Natural Armor',
type: 'armor',
data: {
ac: actor.data.data.naturalArmor,
equipped: true
}
}
await actor.createEmbeddedDocuments('Item', [naturalArmor], {parent: actor})
}

return actor;
}
const startMonsterProcess = async (folders) => {
const monsters = await fetch('modules/chromatic-dungeons-compendia/rawdata/monsters.json')
.then(res => res.json())
.then(...) // All of my data prep for creating monsters
.then(createMonsters)
.then(actors => actors.map(createMonsterWeapons))
.then(actors => actors.map(createMonsterArmor));
}

const createMonsters = async (items) =>
await Actor.create(items);

const createMonsterWeapons = async (actor) => {
const attackKeys = Object.keys(actor.data.data.attackObjects);

const attackItems = attackKeys.map(
(key) => {
... // Prepare data for the returned object
return ({
name,
type: 'weapon',
data: {
damage,
equipped: true
}
});
}
).filter(item => !!item);



if (attackItems.length)
await actor.createEmbeddedDocuments('Item', attackItems, {parent: actor});

return await actor;
}

const createMonsterArmor = async (actor) => {
actor = await actor;

if (actor.data.data.naturalArmor) {
const naturalArmor = {
name: 'Natural Armor',
type: 'armor',
data: {
ac: actor.data.data.naturalArmor,
equipped: true
}
}
await actor.createEmbeddedDocuments('Item', [naturalArmor], {parent: actor})
}

return actor;
}
Ethaks
Ethaks3y ago
That looks like a ton of individual requests sent to the server, which Foundry does not like all that much. When using createEmbeddedDocuments, you shouldn't need a parent option btw. Question upfront: Given only the raw actor data from the JSON, can you already derive which weapons etc. are to be created?
wyrmisis
wyrmisis3y ago
I was originally trying to do this without the parent object, that was a "throw everything at this and see if anything sticks" change Yep! The steps to generate these items are done after Actor creation, so the data's on the Actor already I prune it off at the end of this process Last I counted, there are 260 Actors being processed through this flow. Each actor has between 0-6 items being created in this way.
Ethaks
Ethaks3y ago
In that case, batching the whole creation might be the best approach, since creating those actors one by one and awaiting each creation will take a while I'll elaborate in a sec
wyrmisis
wyrmisis3y ago
You're saying, create the actors, then put together all items that are going in at the same time, then create the items? Or is there a way to create embedded items at the same time that you create a batch of actors?
Ethaks
Ethaks3y ago
So, something neat you can do: You can create a local instance of an actor by const actor = new Actor(actorData), and similarly, you can create in-memory instances of Items by using const item = new Item(itemData). To add an item like that to an actor that has not yet been created on the server (which makes createEmbeddedDocuments useless, since it only works with actors that already have an ID), you can then actor.data.update({items: [item.toObject()]}). This adds the raw item data to the actor's source data, which you can eventually send to the server via Actor.create(actor.toObject())
wyrmisis
wyrmisis3y ago
Oh neat, so I'd be able to lump those local actors into an array and push them up to the server all at once, yeah? With their items already attached?
Ethaks
Ethaks3y ago
Yep, if you have an array of Actor instances, you can send a single request to the server with Actor.createDocuments(actors.map(a => a.toObject())) If that is possible in your case (data preparation can be a bit weird with in-memory actors), that could allow you to only send a single document creation request to the server, containing all actors with their item data already in their source data
wyrmisis
wyrmisis3y ago
I feel like that should def be doable (then again, I've been wrong about "I should be able to do this" before 😂 ) I'll give it a try and get back to you, thanks for pointing me in (what is hopefully) the right direction!
ccjmk
ccjmk3y ago
@Ethaks , i think there might be an issue with that, depending on his scenario - oor i did something pretty bad in my module 😛 I reckon he's creating multiple actors at the same time though, i create just one but when I added items directly into the actor, it didn't transfer properly item's active effects though now that i think of it, i didn't create an in-memory actor First, updated with items, and then created the actor as you mention, I sort of created the actor with stuff in one fell sweep, so that might be part of the problem but at the very least, I'd recommend @wyrmisis to try using items with active effects for a quick test, if its relevant to them
Ethaks
Ethaks3y ago
Did you create the actor with actor data that already contained items which already contained active effects?
ccjmk
ccjmk3y ago
.... yes hahahha
Ethaks
Ethaks3y ago
Yeah, that might bypass the regular AE creation workflow, since that one's probably triggered by stuff getting added, not by it magically existing
ccjmk
ccjmk3y ago
but if your idea works, that would actually be a good improvement I think.. let me show real quick
// newActorData comes here with alll the stuff, name, token data, all items, attributes, etc
const itemsFromActor = newActorData.items; // moving items to a different object to process active effects
newActorData.items = [];
const cls = getDocumentClass('Actor');
const actor = new cls(newActorData);
const newActor = await Actor.create(actor.toObject());
await newActor.createEmbeddedDocuments('Item', itemsFromActor as any);
// newActorData comes here with alll the stuff, name, token data, all items, attributes, etc
const itemsFromActor = newActorData.items; // moving items to a different object to process active effects
newActorData.items = [];
const cls = getDocumentClass('Actor');
const actor = new cls(newActorData);
const newActor = await Actor.create(actor.toObject());
await newActor.createEmbeddedDocuments('Item', itemsFromActor as any);
i removed some error checkings and console.logging in the middle, but that's the core of it, and i end up making two calls to the server, one to create the actor without the items, one with the items from what i gathered, this could be..
const itemsFromActor = newActorData.items; // moving items to a different object to process active effects
newActorData.items = [];
const cls = getDocumentClass('Actor');
const actor = new cls(newActorData);
actor.data.update({items: itemsFromActor.map(i => i.toObject()});
const newActor = await Actor.create(actor.toObject());
//await newActor.createEmbeddedDocuments('Item', itemsFromActor as any);
const itemsFromActor = newActorData.items; // moving items to a different object to process active effects
newActorData.items = [];
const cls = getDocumentClass('Actor');
const actor = new cls(newActorData);
actor.data.update({items: itemsFromActor.map(i => i.toObject()});
const newActor = await Actor.create(actor.toObject());
//await newActor.createEmbeddedDocuments('Item', itemsFromActor as any);
Ethaks
Ethaks3y ago
What is items there? The toObject part is specific to Item/ItemData instances And even then AE might be problematic, but you'd have to tinker around with it. The main issue with them is that Items can have embedded documents (AEs), but only when they're not embedded themselves – they're supposed to give their children to the actor in that case. I'd have to look at Foundry's code to see where that transfer happens though.
ccjmk
ccjmk3y ago
you mean the one I originally take from newActorData.items on the first line? its an array of items taken from compendiums
Ethaks
Ethaks3y ago
Hm, not sure how much of a difference this'd make then tbh. The actor.data.update pretty much only adds the raw item data into the source data items array.
wyrmisis
wyrmisis3y ago
FWIW, @Ethaks 's approach worked for me, so thanks. 😄 There's a certain flavor of irony to spending more time on this importer script that I'll run like once than any other feature players will actually touch.
Leo The League Lion
@wyrmisis gave vote LeaguePoints™ to @Ethaks (#6 • 253)