--- title: Replikant Plans description: Guid explaining the planned changes in the Replicant codebase --- ## Goals: * Reduce the size and responsibility of the giant blueprints which have to be modified frequently * Automatically handle the common operations (subscribing to OSC messages, saving data) * Less manual work later * Less common Blueprints to modify * Support Undo/Redo operations (with minimal extra work per feature) * Extendable, easy to work with system ## Design: A system built to manage different Object based blueprints. Extendable via creating new Blueprints. ### 1) Handlers: ```cpp= // Abstract parent class for the Handler blueprint objects class UDNAHandler : public UObject { // Fill it in child class // to get a HandleOSCMessage() call for the specified addresses UPROPERTY(EditAnywhere) TArray<FName> MessagesToHandle; // ID is one of the members of MessagesToHandle UFUNCTION(BlueprintImplementableEvent) void HandleOSCMessage(FName ID, const FOSCMessage& Message); // We can also add implementable events for any event which // might need to be handled: UFUNCTION(BlueprintImplementableEvent) void OnSceneLoaded(); UFUNCTION(BlueprintImplementableEvent) void OnSceneReset(); // ... }; ``` * Handler examples: * SelectionHandler * TransformHandler * SkeletalMeshMaterialHandler * CommandHandler * etc. * All the Blueprint children must be placed in `Content/Blueprints/Handlers` They are parsed from that subfolder and spawned runtime * To have a new Handler: * Create a new Blueprint based on `UDNAHandler` * Add the desired OSC addresses to the `MessagesToHandle` array * Input handling and anything which requires a component won't work with Objects (e.g. Timelines) We could have Actor based Handlers as well: * base Actor class either * implementing a shared interface with `UDNAHandler`, or * containing an `UDNAHandler` which forwards the function calls * they could support both automatic spawn or manual level placement options * Most of the logic should be in the `Handler` classes, however HandleOSCMessage typically does not execute them directly: * Handler spawns `UDNACommand` object and sends it to the `CommandHandler` * `CommandHandler` calls Execute() on the command object * The object can call functions on the `Handler` --- ### 2) Commands: ```cpp /** * Abstract parent class for the Command blueprint objects * Commands are spawned runtime when they have to be executed * All parameters which are required for the execution must be passed on spawn * The Command is sent to the CommandHandler, which handles the execution */ class UDNACommand : public UObject { /** * 1.) Store the variables required to revert the execution * 2.) Execute the command * * @return True if the operation succeeded (and it can be reverted later) */ UFUNCTION(BlueprintImplementableEvent) bool Execute(); // Commands which can not fail can implement OnExecute() bool Execute_Implementation(UDNACommand* Other) { OnExecute(); return true; } UFUNCTION(BlueprintImplementableEvent) void OnExecute(); // The command must be undone (using the variables saved in Execute()) UFUNCTION(BlueprintImplementableEvent) void Revert(); /** * Commands has to be merged for ongoing operations * (E.g. if something is changed with a slider * the command should only be closed when the user releases the slider) * * We can get touch state OSC messages and have an int (CommandSequenceID) * in the CommandHandler increased each time a widget lose touch * The CommandSequenceIDs are also passed on to the commands on execution * New Command will be merged to the last one if * -> SequenceID in Command matches the one in CommandHandler * -> Last executed Command class is the same as the new command class * -> CanBeCombinedWith returns true */ UFUNCTION(BlueprintImplementableEvent) bool CanBeCombinedWith(UDNACommand* Other); bool CanBeCombinedWith_Implementation(UDNACommand* Other) { return true; } /** * Called on a command which was already Executed() * The parameter is a command with the same class but new parameters * * The operation must be executed as if the new command is executed * after the old one, but the result of the Revert() call should be the same * (It has to restore the app to the state it was before the first command was * executed). * * After ApplyCommand is called the old command is kept, the new one is dropped * Multiple ApplyCommands can be called after each other */ UFUNCTION(BlueprintImplementableEvent) void ApplyCommand(UDNACommand* Other); }; ``` --- ### 3) Save Data: ```cpp class UDNASaveData : public UObject { UPROPERTY(EditAnywhere) EDNASaveType SaveType = EDNASaveType::Scene; }; ``` ### * Basic logic is similar to `UDNAHandler`: * All the BP children must go to `Content/Blueprints/SaveData/` * They are parsed from that subfolder and spawned runtime * They can be associated with different save files (Scene, Avatar) * They should only contain data * The whole class is saved to and loaded from the associated save file automatically * Can be accessed via Custom editor node: `GetSaveData` * Input: `UClass` of an `UDNASaveData` child Blueprint * Output: BP object casted to proper save data class * `GetAvatarSaveData` variant with index input pin (Sidekicks should also be Avatars) ### 4) Other notes: Some of the new features we need require bigger changes, they should be handled during the transition to the new system. * Sidekicks should be handled as Avatars * Dynamically spawned instead of loading them with streaming levels to be more flexible * They should support all editing features Avatars do * multiple Avatars in a scene instead of having one Avatar + Sidekicks * Multi-selection support: * When Multiple object is selected the UI commands should be executed on the ones which support the operation * In-app UI: * Should use the same interface as the external UI whenever possible * Custom Widgets for easier event subscription