Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "dedicated server" export mode which can strip unneeded visual resources (superseded) #69546

Closed
wants to merge 1 commit into from

Conversation

dsnopek
Copy link
Contributor

@dsnopek dsnopek commented Dec 3, 2022

This implements a little more of godotengine/godot-proposals#2756 (but not all of it - this will take a few PRs), including:

  • There is an "Is server" option that you can enable when configuring a Windows, MacOS or Linux export preset
  • A "dedicated_server" feature tag will be set when running one of those exports!
    • So, you'd detect that you are a dedicated server via OS.has_feature("dedicated_server").
    • This is different than the proposal, which proposes Engine.is_dedicated_server(), but I think a feature tag is better, because then you can override project settings using the tag (ie. using "setting.dedicated_server"), and features are commonly used to identify things about the platform. If others feel strongly, though, I can add Engine.is_dedicated_server() as well.
  • When running an export with "Is server" enabled, headless mode is forced (so, the equivalent of --headless on the CLI)
  • Resources get a "Dedicated Server Export Type" which can be set to "Keep" or "Strip"
    • For imported resources, this is set on the import options:
      Selection_436
    • For embedded resources, you can set it under the expandable "Resource" group in the inspector:
      Selection_437
    • "Remove" and "RemoveOnClient" from the proposal aren't implemented yet. We can add those in a future PR.
  • When exporting on a preset with "Is server" enabled, resources set to "Strip" will be replaced with their placeholder versions from Implement placeholder assets #60583.
    • "Strip" is the default, but it doesn't do anything on resources that don't have a placeholder version. We'll probably be adding more placeholder resources in future PRs.
  • Random note: Overall, I tried to keep the extra code added for this feature as editor-only, so that it doesn't have an impact on the exported games.

This is was marked as a draft (although, no longer is) because:

  • There is an issue with the "file export cache" that I haven't solved yet. If you change the "Dedicated Server Export Type" on an imported asset, re-import and then export, it will export it as-if it still had the previous setting. However, if I delete .godot/exported/*/file_cache and then re-export, it gets picked up fine.
  • I'm unsure that using an EditorExportPlugin is the right choice? A lot of the code for this is scattered all over the place (because it has to be), and so dedicated_server_export_plugin.cpp is actually pretty small. So, the plugin seems not totally worth it.
  • I wasn't previously very familiar with the code I'm changing here, so I welcome feedback from more experienced folks! It's very likely I didn't choose the best approach for some or all of this :-)

@dsnopek dsnopek added this to the 4.x milestone Dec 3, 2022
@dsnopek dsnopek requested review from a team as code owners December 3, 2022 23:35
@dsnopek dsnopek marked this pull request as draft December 3, 2022 23:35
@dsnopek dsnopek requested a review from Faless December 3, 2022 23:35
@dsnopek dsnopek force-pushed the server-export branch 2 times, most recently from 43051e5 to 8d75691 Compare December 4, 2022 15:54
@dsnopek
Copy link
Contributor Author

dsnopek commented Dec 4, 2022

My latest push fixes the issue with the file export cache! When checking if the cached file is usable, it's is now looking at the mtime for the .import file as well. This matches the md5 check, which was already checking both the source file and the .import file.

Even though I'm still not super confident about the approaches I chose in this PR, I'm going to take it out of draft since there isn't anything in particular that I'm planning to change - at least until I get some feedback that is :-)

@dsnopek dsnopek marked this pull request as ready for review December 4, 2022 16:27
@dsnopek dsnopek requested a review from a team as a code owner December 4, 2022 16:27
@dsnopek dsnopek changed the title [WIP] Add "dedicated server" export mode which can strip unneeded visual resources Add "dedicated server" export mode which can strip unneeded visual resources Dec 4, 2022
@jordo
Copy link
Contributor

jordo commented Dec 14, 2022

What do you think about the term dedicated_server in general (Is anything really dedicated anymore?). In general it seems industry is moving away from the old term (I haven't heard this referenced in quite sometime). Would gameserver be sufficient? Online gameserver?

@jordo
Copy link
Contributor

jordo commented Dec 14, 2022

Is the defaulted export type strip or keep?

Copy link
Contributor

@jordo jordo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great stuff! Left a couple comments there.

Comment on lines +74 to +123
if (const Texture2D *texture = Object::cast_to<Texture2D>(p_resource.ptr())) {
Ref<PlaceholderTexture2D> placeholder;
placeholder.instantiate();
placeholder->set_size(texture->get_size());
return placeholder;
}

if (const Texture2DArray *texture = Object::cast_to<Texture2DArray>(p_resource.ptr())) {
Ref<PlaceholderTexture2DArray> placeholder;
placeholder.instantiate();
placeholder->set_size(Size2i(texture->get_width(), texture->get_height()));
placeholder->set_layers(texture->get_layers());
return placeholder;
}

if (const Texture3D *texture = Object::cast_to<Texture3D>(p_resource.ptr())) {
Ref<PlaceholderTexture3D> placeholder;
placeholder.instantiate();
placeholder->set_size(Vector3i(texture->get_width(), texture->get_height(), texture->get_depth()));
return placeholder;
}

if (const Cubemap *cubemap = Object::cast_to<Cubemap>(p_resource.ptr())) {
Ref<PlaceholderCubemap> placeholder;
placeholder.instantiate();
placeholder->set_size(Size2i(cubemap->get_width(), cubemap->get_height()));
placeholder->set_layers(cubemap->get_layers());
return placeholder;
}

if (const CubemapArray *cubemap = Object::cast_to<CubemapArray>(p_resource.ptr())) {
Ref<PlaceholderCubemapArray> placeholder;
placeholder.instantiate();
placeholder->set_size(Size2i(cubemap->get_width(), cubemap->get_height()));
placeholder->set_layers(cubemap->get_layers());
return placeholder;
}

if (Object::cast_to<Material>(p_resource.ptr()) != nullptr) {
Ref<PlaceholderMaterial> placeholder;
placeholder.instantiate();
return placeholder;
}

if (const Mesh *mesh = Object::cast_to<Mesh>(p_resource.ptr())) {
Ref<PlaceholderMesh> placeholder;
placeholder.instantiate();
placeholder->set_aabb(mesh->get_aabb());
return placeholder;
}
Copy link
Contributor

@jordo jordo Dec 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic here, (instantiating a placeholder resource which derives from a source resource), I don't think this is the best place for it ( dedicated_server_export_plugin.cpp). It seems like each class itself should be responsible for understanding how to create a placeholder version of itself... Perhaps each of these resources should have a to_placeholder() or similar function, and dedicated_server_export_plugin.cpp can be consumer of those interfaces instead of dedicated_server_export_plugin.cpp requiring knowledge of these details and being the implementer for each type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great idea! That would certainly make it easier to progressively add more placeholders. I'll work this into the next revision.

Comment on lines +1355 to +1356
audio_driver = "Dummy";
display_driver = "headless";
Copy link
Contributor

@jordo jordo Dec 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be great if these weren't magic strings... (Not sure if there is a global somewhere, but would be a great refactor to add one if there isn't)/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These same magic strings are hardcoded for the "--headless" CLI option:

https://github.com/godotengine/godot/pull/69546/files/d0b991ad029e7f038ce53665067c8ea7637000ea#diff-18b7c19417a461e4793bb2157831659d96ede991391e10ee5bc51f19453b7685L978

We could maybe have some constants for these at least?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya, that's what I suspected, which is why I was thinking thinking it would would be a good refactor & consolidate these into a common shared constant.

@Calinou
Copy link
Member

Calinou commented Dec 15, 2022

Would gameserver be sufficient? Online gameserver?

headless_server sounds better to me, as it avoids using the term "game" (we try to refer to Godot apps as "projects" when possible). Also, not all headless servers are used in an online (Internet) environment – some might be running on a LAN only.

Copy link
Collaborator

@Faless Faless left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the technical comment below regarding the API consistency.

Regarding the design, I'm not particularly fond of this approach either, and I think we could try something differnt which moves the responsability completely to the exporter, leaving the importers untouched. Let me try to explain.

I think in the long run we could aim at something like we have for the resource exporter:

resource_exporter

I'm not the best UX designer, but the idea is to have at least the following options:

  • Dedicated server export: on/off
  • Replace supported assets with placeholders: All/Selected
  • Resources to preseve/replace (a Tree node with the resource list like in the above screenshot)

Then the DedicatedServerExportPlugin would simply check the export option, and the inclusion/exclusion list to decide if a resource with a valid placeholder should be replaced.

We could then iterate on this and maybe add a separate option which allows you to select classes or filter by path if the needs arise.

Does this sound reasonable?

Comment on lines +441 to +448
#ifdef TOOLS_ENABLED
ClassDB::bind_method(D_METHOD("set_dedicated_server_export_type", "server_export_type"), &Resource::set_dedicated_server_export_type);
ClassDB::bind_method(D_METHOD("get_dedicated_server_export_type"), &Resource::get_dedicated_server_export_type);
ADD_PROPERTY(PropertyInfo(Variant::INT, "resource_dedicated_server_export_type", PROPERTY_HINT_ENUM, "Strip,Keep"), "set_dedicated_server_export_type", "get_dedicated_server_export_type");

BIND_ENUM_CONSTANT(DEDICATED_SERVER_EXPORT_STRIP);
BIND_ENUM_CONSTANT(DEDICATED_SERVER_EXPORT_KEEP);
#endif
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I know this breaks the API compatibility between editor and templates.
Godot has 2 API levels, a "core" one, and and "editor" one (see ClassDB::API_CORE and ClassDB::API_EDITOR usage in main.cpp).
The 2 API levels should not be mixed, and the ClassDB::API_CORE should be the same between editor and templates.

If we go with this approach of special methods which only works in editor, we should still bind the methods and constants in templates, and leave them with an empty implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this was me attempting to not weight the templates down with APIs that they don't need. If we end up keeping something on Resource, I'll dig into ClassDB::API_CORE vs ClassDB::API_EDITOR and see if there's a better way to make these properties editor only.

@dsnopek
Copy link
Contributor Author

dsnopek commented Dec 15, 2022

@jordo Thanks for the review!

What do you think about the term dedicated_server in general (Is anything really dedicated anymore?). In general it seems industry is moving away from the old term (I haven't heard this referenced in quite sometime). Would gameserver be sufficient? Online gameserver?

I took "dedicated_server" from the proposal, but I'm fine with whatever the consensus ends up being!

I think I like @Calinou suggestion of "headless_server" better than "gameserver", since the distinction here is that this is only a server, not a client that also has a server mode or something like that.

Is the defaulted export type strip or keep?

The default is "strip" per the proposal.

@dsnopek
Copy link
Contributor Author

dsnopek commented Dec 15, 2022

@Faless Thanks for the review!

Regarding the design, I'm not particularly fond of this approach either, and I think we could try something differnt which moves the responsability completely to the exporter, leaving the importers untouched. Let me try to explain.
[...]
I'm not the best UX designer, but the idea is to have at least the following options:

  • Dedicated server export: on/off
  • Replace supported assets with placeholders: All/Selected
  • Resources to preseve/replace (a Tree node with the resource list like in the above screenshot)

Then the DedicatedServerExportPlugin would simply check the export option, and the inclusion/exclusion list to decide if a resource with a valid placeholder should be replaced.

That makes sense, and would definitely handle getting out of the importer, making this feature touch fewer systems, which is nice.

However, how would we handle embedded resources (ie. like a mesh that's part of a scene, not saved to an external .tres file)? From a technical perspective, we are able to replace embedded resources with placeholders just fine, and this is something we probably want to have for 3D, since it's not uncommon to have mesh data embedded in a .tscn. (EDIT: In fact, I think that's the most common case, since if you drop a .glb into your project the imported .tscn will have meshes embedded by default.)

How do we allow developers to configure which embedded resources to strip or keep?

The current approach in the PR is nice in that external and embedded resources are "unified". It's the resource->get/set_dedicated_server_export_type() that decides if we're going to strip that resource, and for embedded resources you set that directly (in the inspector when working on the scene that has the resource) and for external resources it's set in the import settings, but ultimately, it ends up in the same place (which is also visible when looking at the imported resource in the inspector).

We could certainly keep the extra property added to Resource for dealing with embedded resources, but switch to a UI in the exporter like you propose for external resources? But that's two separate systems for each (rather than a "unified" one).

Or, we could try to include embedded resources in the Tree node in the exporter UI? But then we'd have load every scene in the project and walk through its scene tree looking for embedded resources, which seems impractical.

@dsnopek dsnopek changed the title Add "dedicated server" export mode which can strip unneeded visual resources Add "dedicated server" export mode which can strip unneeded visual resources (superceded) Dec 20, 2022
@dsnopek
Copy link
Contributor Author

dsnopek commented Dec 20, 2022

This PR is superseded by #70377

@dsnopek dsnopek changed the title Add "dedicated server" export mode which can strip unneeded visual resources (superceded) Add "dedicated server" export mode which can strip unneeded visual resources (superseded) Dec 20, 2022
@aaronfranke aaronfranke removed this from the 4.x milestone Feb 1, 2023
@dsnopek dsnopek deleted the server-export branch July 22, 2024 15:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants