Browse Source

Squashed commit of the following:

commit 62a6eeb3ce422d7680bbbdefc50b6160bf9b47b9
Author: Anton Tarasenko <dkanus@gmail.com>
Date:   Sat Sep 3 04:59:17 2022 +0700

    Finish `feature` command implementation

commit de24fc4f7b
Author: Anton Tarasenko <dkanus@gmail.com>
Date:   Wed Aug 17 04:45:22 2022 +0700

    Refactor to separate pending config logic

commit 6597c17c51
Author: Anton Tarasenko <dkanus@gmail.com>
Date:   Mon Aug 15 05:17:57 2022 +0700

    Add working, but dirty "feature editconfig"
develop
Anton Tarasenko 2 years ago
parent
commit
e40883134f
  1. 570
      sources/Commands/ACommandFeature.uc
  2. 388
      sources/Commands/ACommandFeature_Announcer.uc
  3. 353
      sources/Tools/PendingConfigsTool.uc

570
sources/Commands/ACommandFeature.uc

@ -17,16 +17,26 @@
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class ACommandFeature extends Command;
class ACommandFeature extends Command
dependson(PendingConfigsTool);
// TODO: autoconf, newconf, deleteconf, setconf
// TODO: when displaying features - display which one is enabled
var private class<Feature> selectedFeatureClass;
var private Text selectedConfigName;
var private ACommandFeature_Announcer announcer;
var private PendingConfigsTool pendingConfigs;
var private ACommandFeature_Announcer announcer;
protected function Constructor()
{
pendingConfigs =
PendingConfigsTool(_.memory.Allocate(class'PendingConfigsTool'));
super.Constructor();
}
protected function Finalizer()
{
_.memory.Free(announcer);
_.memory.Free(pendingConfigs);
super.Finalizer();
}
@ -34,123 +44,271 @@ protected function BuildData(CommandDataBuilder builder)
{
builder.Name(P("feature")).Group(P("admin"))
.Summary(P("Managing features."))
.Describe(P("Command for displaying and enabling/disabling features."));
.Describe(P("Command for managing features and their configs."));
builder.SubCommand(P("enable"))
.ParamText(P("feature"))
.OptionalParams()
.ParamText(P("config"))
.Describe(P("Enables specified <feature>."));
.Describe(P("Enables specified <feature>. If <config> isn't specified -"
@ "choses the \"default\" one, making new config with default"
@ "settings if it doesn't exist."));
builder.SubCommand(P("disable"))
.ParamText(P("feature"))
.Describe(P("Disables specified <feature>."));
builder.SubCommand(P("showconf"))
.ParamText(P("feature"))
.OptionalParams()
.ParamText(P("config"))
.Describe(P("Show given <config> for the given <feature>."));
builder.SubCommand(P("editconf"))
.ParamText(P("feature"))
.ParamText(P("config"))
.ParamText(P("variable_path"))
.ParamRemainder(P("value"))
.Describe(P("Changes a value inside given <config> of the given"
@ "<feature> by setting value at JSON path <variable_path> to"
@ "the JSON value <value>. Changes won't be immediately applied to"
@ "the game and kept as pending."));
builder.SubCommand(P("saveconf"))
.ParamText(P("feature"))
.ParamText(P("config"))
.Describe(P("Saves pending changes for the given <config> of the given"
@ "<feature>."));
builder.SubCommand(P("newconf"))
.ParamText(P("feature"))
.ParamText(P("config"))
.Describe(P("Creates new config for the given <feature>."));
builder.SubCommand(P("removeconf"))
.ParamText(P("feature"))
.ParamText(P("config"))
.Describe(P("Removes specified <config> of the specified <feature>."));
builder.SubCommand(P("autoconf"))
.ParamText(P("feature"))
.OptionalParams()
.ParamText(P("config"))
.Describe(P("Changes current auto config config of the specified"
@ "<feature>. Auto config is a config that is supposed to be"
@ "automatically enabled at the start of the Acedia, unless"
@ "otherwise specified for the loader."));
builder.Option(P("all"))
.Describe(F("Affects subcommand {$TextEmphasis showconf} by making it"
@ "show all available configs."));
builder.Option(P("save"))
.Describe(F("Affects subcommand {$TextEmphasis editconf} by making it"
@ "also save all pending changes."));
announcer = ACommandFeature_Announcer(
_.memory.Allocate(class'ACommandFeature_Announcer'));
}
protected function Executed(CallData arguments, EPlayer instigator)
{
local bool saveFlag, allFlag;
announcer.Setup(none, instigator, othersConsole);
saveFlag = arguments.options.HasKey(P("save"));
allFlag = arguments.options.HasKey(P("all"));
SelectFeatureAndConfig(arguments);
if (arguments.subCommandName.IsEmpty()) {
ShowAllFeatures();
}
else if (arguments.subCommandName.Compare(P("enable")))
{
EnableFeature(
arguments.parameters.GetText(P("feature")),
arguments.parameters.GetText(P("config")));
else if (arguments.subCommandName.Compare(P("enable"))) {
EnableFeature();
}
else if (arguments.subCommandName.Compare(P("disable"))) {
DisableFeature(arguments.parameters.GetText(P("feature")));
DisableFeature();
}
else if (arguments.subCommandName.Compare(P("showconf"))) {
ShowSelectedConfigs(allFlag);
}
else if (arguments.subCommandName.Compare(P("editconf")))
{
EditFeatureConfig(
arguments.parameters.GetText(P("variable_path")),
arguments.parameters.GetText(P("value")),
saveFlag);
}
else if (arguments.subCommandName.Compare(P("saveconf"))) {
SaveFeatureConfig();
}
else if (arguments.subCommandName.Compare(P("newconf"))) {
NewFeatureConfig();
}
else if (arguments.subCommandName.Compare(P("removeconf"))) {
RemoveFeatureConfig();
}
else if (arguments.subCommandName.Compare(P("autoconf"))) {
SetAutoFeatureConfig();
}
_.memory.Free(selectedConfigName);
selectedConfigName = none;
}
protected function EnableFeature(BaseText featureName, BaseText configParameter)
protected function SelectFeatureAndConfig(CallData arguments)
{
local bool wasEnabled;
local Text oldConfig, newConfig;
local Feature instance;
local class<Feature> featureClass;
local Text userGivenFeatureName, userGivenConfigName;
featureClass = LoadFeatureClass(featureName);
if (featureClass == none) {
userGivenFeatureName = arguments.parameters.GetText(P("feature"));
selectedFeatureClass = LoadFeatureClass(userGivenFeatureName);
if (selectedFeatureClass == none && !arguments.subCommandName.IsEmpty()) {
return;
}
wasEnabled = featureClass.static.IsEnabled();
oldConfig = featureClass.static.GetCurrentConfig();
newConfig = GetConfigFromParameter(configParameter, featureClass);
_.memory.Free(userGivenFeatureName);
userGivenConfigName = arguments.parameters.GetText(P("config"));
if (userGivenConfigName != none)
{
selectedConfigName = userGivenConfigName.LowerCopy();
userGivenConfigName.FreeSelf();
}
pendingConfigs.SelectConfig(selectedFeatureClass, selectedConfigName);
}
protected function EnableFeature()
{
local bool wasEnabled;
local Text oldConfig, newConfig;
local Feature instance;
wasEnabled = selectedFeatureClass.static.IsEnabled();
oldConfig = selectedFeatureClass.static.GetCurrentConfig();
newConfig = PickConfigBasedOnParameter();
// Already enabled with the same config!
if (oldConfig != none && oldConfig.Compare(newConfig, SCASE_INSENSITIVE))
{
announcer.AnnounceFailedAlreadyEnabled(featureClass, newConfig);
announcer.AnnounceFailedAlreadyEnabled(selectedFeatureClass, newConfig);
_.memory.Free(newConfig);
_.memory.Free(oldConfig);
return;
}
// Try enabling and report the result
instance = featureClass.static.EnableMe(newConfig);
if (instance == none) {
announcer.AnnounceFailedCannotEnableFeature(featureClass, newConfig);
instance = selectedFeatureClass.static.EnableMe(newConfig);
if (instance == none)
{
announcer.AnnounceFailedCannotEnableFeature(
selectedFeatureClass,
newConfig);
}
else if (wasEnabled) {
announcer.AnnounceSwappedConfig(featureClass, oldConfig, newConfig);
else if (wasEnabled)
{
announcer.AnnounceSwappedConfig(
selectedFeatureClass,
oldConfig,
newConfig);
}
else {
announcer.AnnounceEnabledFeature(featureClass, newConfig);
announcer.AnnounceEnabledFeature(selectedFeatureClass, newConfig);
}
_.memory.Free(newConfig);
_.memory.Free(oldConfig);
}
protected function DisableFeature(Text featureName)
protected function DisableFeature()
{
local class<Feature> featureClass;
featureClass = LoadFeatureClass(featureName);
if (featureClass == none) {
return;
}
if (!featureClass.static.IsEnabled())
if (!selectedFeatureClass.static.IsEnabled())
{
announcer.AnnounceFailedAlreadyDisabled(featureClass);
announcer.AnnounceFailedAlreadyDisabled(selectedFeatureClass);
return;
}
featureClass.static.DisableMe();
selectedFeatureClass.static.DisableMe();
// It is possible that this command itself is destroyed after above command
// so do the check just in case
if (IsAllocated()) {
announcer.AnnounceDisabledFeature(featureClass);
announcer.AnnounceDisabledFeature(selectedFeatureClass);
}
}
protected function Text GetConfigFromParameter(
BaseText configParameter,
class<Feature> featureClass)
protected function ShowSelectedConfigs(bool showAllFeatures)
{
local Text resolvedConfig;
local int i;
local array<Text> availableConfigs;
local MutableText configList;
local class<FeatureConfig> configClass;
if (featureClass == none) {
return none;
if (showAllFeatures)
{
configClass = selectedFeatureClass.default.configClass;
if (configClass != none) {
availableConfigs = configClass.static.AvailableConfigs();
}
for (i = 0; i < availableConfigs.length; i += 1) {
ShowFeatureConfig(availableConfigs[i]);
}
_.memory.FreeMany(availableConfigs);
return;
}
configClass = featureClass.default.configClass;
if (selectedConfigName != none)
{
ShowFeatureConfig(selectedConfigName);
return;
}
configList = PrintConfigList(selectedFeatureClass);
callerConsole
.Flush()
.Write(P("Available configs: "))
.WriteLine(configList);
_.memory.Free(configList);
}
protected function ShowFeatureConfig(BaseText configName)
{
local MutableText dataAsJSON;
local HashTable currentData, pendingData;
if (configName == none) {
return;
}
currentData = GetCurrentConfigData(configName);
if (currentData == none)
{
announcer.AnnounceFailedNoDataForConfig(
selectedFeatureClass,
configName);
return;
}
// Display current data
dataAsJSON = _.json.PrettyPrint(currentData);
announcer.AnnounceCurrentConfig(selectedFeatureClass, configName);
callerConsole.Flush().WriteLine(dataAsJSON);
_.memory.Free(dataAsJSON);
// Display pending data
pendingConfigs.SelectConfig(selectedFeatureClass, configName);
pendingData = pendingConfigs.GetPendingConfigData();
if (pendingData != none)
{
dataAsJSON = _.json.PrettyPrint(pendingData);
announcer.AnnouncePendingConfig(
selectedFeatureClass,
configName);
callerConsole.Flush().WriteLine(dataAsJSON);
_.memory.Free(dataAsJSON);
}
_.memory.Free(pendingData);
_.memory.Free(currentData);
}
protected function Text PickConfigBasedOnParameter()
{
local Text resolvedConfig;
local class<FeatureConfig> configClass;
configClass = selectedFeatureClass.default.configClass;
if (configClass == none)
{
announcer.AnnounceFailedNoConfigClass(featureClass);
announcer.AnnounceFailedNoConfigClass(selectedFeatureClass);
return none;
}
// If config was specified - simply check that it exists
if (configParameter != none)
if (selectedConfigName != none)
{
if (configClass.static.Exists(configParameter)) {
return configParameter.Copy();
if (configClass.static.Exists(selectedConfigName)) {
return selectedConfigName.Copy();
}
announcer.AnnounceFailedConfigMissing(configParameter);
announcer.AnnounceFailedConfigMissing(selectedConfigName);
return none;
}
// If it wasn't specified - try auto config instead
resolvedConfig = configClass.static.GetAutoEnabledConfig();
if (resolvedConfig == none) {
announcer.AnnounceFailedNoConfigProvided(featureClass);
announcer.AnnounceFailedNoConfigProvided(selectedFeatureClass);
}
return resolvedConfig;
}
@ -186,59 +344,299 @@ protected function ShowAllFeatures()
}
}
protected function ShowFeature(class<Feature> feature)
protected function ShowFeature(class<Feature> featureClass)
{
local int i;
local Text autoConfig;
local MutableText featureName, builder;
local ListBuilder configList;
local array<Text> availableConfigs;
local class<FeatureConfig> configClass;
local MutableText featureName;
local MutableText configList;
if (feature == none) {
if (featureClass == none) {
return;
}
configClass = feature.default.configClass;
if (configClass != none) {
availableConfigs = configClass.static.AvailableConfigs();
}
featureName = _.text
.FromClassM(feature)
.FromClassM(featureClass)
.ChangeDefaultColor(_.color.TextEmphasis);
builder = _.text.Empty();
if (feature.static.IsEnabled()) {
builder.Append(F("[ {$TextPositive enabled} ] "));
configList = PrintConfigList(featureClass);
callerConsole.Flush();
if (featureClass.static.IsEnabled()) {
callerConsole.Write(F("[ {$TextPositive enabled} ] "));
}
else {
builder.Append(F("[ {$TextNegative disabled} ] "));
callerConsole.Write(F("[ {$TextNegative disabled} ] "));
}
builder.Append(featureName);
callerConsole.Write(featureName)
.Write(P(" with configs: "))
.WriteLine(configList);
_.memory.Free(featureName);
if (availableConfigs.length == 1) {
builder.Append(P(" with config:"));
}
else if (availableConfigs.length > 1) {
builder.Append(P(" with configs:"));
}
callerConsole.Write(builder);
_.memory.Free(builder);
configList = ListBuilder(_.memory.Allocate(class'ListBuilder'));
autoConfig = configClass.static.GetAutoEnabledConfig();
_.memory.Free(configList);
}
protected function MutableText PrintConfigList(class<Feature> featureClass)
{
local int i;
local Text autoConfig;
local ListBuilder configList;
local MutableText result, nextConfig;
local array<Text> availableConfigs;
local class<FeatureConfig> configClass;
if (featureClass == none) return none;
configClass = featureClass.default.configClass;
if (configClass == none) return none;
availableConfigs = configClass.static.AvailableConfigs();
autoConfig = configClass.static.GetAutoEnabledConfig();
configList = ListBuilder(_.memory.Allocate(class'ListBuilder'));
for (i = 0; i < availableConfigs.length; i += 1)
{
configList.Item(availableConfigs[i]);
nextConfig = availableConfigs[i].MutableCopy();
if (pendingConfigs.HasPendingConfigFor(featureClass, nextConfig)) {
nextConfig.Append(F("{$TextEmphasis *}"));
}
configList.Item(nextConfig);
_.memory.Free(nextConfig);
if ( autoConfig != none
&& autoConfig.Compare(availableConfigs[i], SCASE_INSENSITIVE))
{
configList.Comment(F("{$TextPositive auto enabled}"));
}
}
builder = configList.GetMutable();
callerConsole.WriteLine(builder);
_.memory.FreeMany(availableConfigs);
result = configList.GetMutable();
_.memory.Free(configList);
_.memory.Free(autoConfig);
_.memory.Free(builder);
_.memory.FreeMany(availableConfigs);
return result;
}
protected function MutableText PrettyPrintValueAt(BaseText pathToValue)
{
local MutableText printedValue;
local AcediaObject value;
local HashTable relevantData;
relevantData = pendingConfigs.GetPendingConfigData();
if (relevantData == none) {
relevantData = GetCurrentSelectedConfigData();
}
if (relevantData != none) {
value = relevantData.GetItemBy(pathToValue);
}
if (value != none)
{
printedValue = _.json.PrettyPrint(value);
_.memory.Free(value);
}
_.memory.Free(relevantData);
return printedValue;
}
protected function EditFeatureConfig(
BaseText pathToValue,
BaseText newValue,
bool saveConfig)
{
local MutableText printedOldValue;
local MutableText printedNewValue;
local PendingConfigsTool.PendingConfigToolResult error;
printedOldValue = PrettyPrintValueAt(pathToValue);
error = pendingConfigs.EditConfig(pathToValue, newValue);
if (error == PCTE_None) {
printedNewValue = PrettyPrintValueAt(pathToValue);
}
if (error == PCTE_ConfigMissing) {
announcer.AnnounceFailedConfigMissing(selectedConfigName);
}
else if (error == PCTE_ExpectedObject) {
announcer.AnnounceFailedExpectedObject();
}
else if (error == PCTE_BadPointer)
{
announcer.AnnounceFailedBadPointer(
selectedFeatureClass,
selectedConfigName,
pathToValue);
}
else if (printedOldValue == none)
{
announcer.AnnounceConfigNewValue(
selectedFeatureClass,
selectedConfigName,
pathToValue,
printedNewValue);
}
else
{
announcer.AnnounceConfigEdited(
selectedFeatureClass,
selectedConfigName,
pathToValue,
printedOldValue,
printedNewValue);
}
if (saveConfig && error == PCTE_None) {
SaveFeatureConfig();
}
_.memory.Free(printedOldValue);
_.memory.Free(printedNewValue);
}
protected function SaveFeatureConfig()
{
local BaseText enabledConfigName;
local HashTable pendingData;
local class<FeatureConfig> configClass;
configClass = selectedFeatureClass.default.configClass;
if (configClass == none)
{
announcer.AnnounceFailedNoConfigClass(selectedFeatureClass);
return;
}
pendingData = pendingConfigs.GetPendingConfigData();
if (pendingData == none)
{
announcer.AnnounceFailedPendingConfigMissing(selectedConfigName);
return;
}
// Make sure config exists
configClass.static.NewConfig(selectedConfigName);
configClass.static.SaveData(selectedConfigName, pendingData);
// Re-apply config if it is active?
enabledConfigName = selectedFeatureClass.static.GetCurrentConfig();
if (selectedConfigName.Compare(enabledConfigName, SCASE_INSENSITIVE))
{
selectedFeatureClass.static.EnableMe(selectedConfigName);
announcer.AnnouncePublicPendingConfigSaved(selectedFeatureClass);
}
else {
announcer.AnnouncePrivatePendingConfigSaved(selectedFeatureClass);
}
_.memory.Free(enabledConfigName);
pendingData.FreeSelf();
pendingConfigs.RemoveConfig();
return;
}
protected function NewFeatureConfig()
{
local BaseText enabledConfigName;
local class<FeatureConfig> configClass;
configClass = selectedFeatureClass.default.configClass;
if (configClass == none)
{
announcer.AnnounceFailedNoConfigClass(selectedFeatureClass);
return;
}
if (configClass.static.Exists(selectedConfigName))
{
announcer.AnnounceFailedConfigAlreadyExists(
selectedFeatureClass,
selectedConfigName);
return;
}
if (!configClass.static.NewConfig(selectedConfigName))
{
announcer.AnnounceFailedBadConfigName(selectedConfigName);
return;
}
enabledConfigName = selectedFeatureClass.static.GetCurrentConfig();
if (selectedConfigName.Compare(enabledConfigName, SCASE_INSENSITIVE))
{
selectedFeatureClass.static.EnableMe(selectedConfigName);
announcer.AnnouncePublicPendingConfigSaved(selectedFeatureClass);
}
_.memory.Free(enabledConfigName);
announcer.AnnounceConfigCreated(selectedFeatureClass, selectedConfigName);
}
protected function RemoveFeatureConfig()
{
local class<FeatureConfig> configClass;
configClass = selectedFeatureClass.default.configClass;
if (configClass == none)
{
announcer.AnnounceFailedNoConfigClass(selectedFeatureClass);
return;
}
if (!configClass.static.Exists(selectedConfigName))
{
announcer.AnnounceFailedConfigDoesNotExist(
selectedFeatureClass,
selectedConfigName);
return;
}
pendingConfigs.RemoveConfig();
configClass.static.DeleteConfig(selectedConfigName);
announcer.AnnounceConfigRemoved(selectedFeatureClass, selectedConfigName);
}
protected function SetAutoFeatureConfig()
{
local Text currentAutoEnabledConfig;
local class<FeatureConfig> configClass;
configClass = selectedFeatureClass.default.configClass;
if (configClass == none)
{
announcer.AnnounceFailedNoConfigClass(selectedFeatureClass);
return;
}
if ( selectedConfigName != none
&& !configClass.static.Exists(selectedConfigName))
{
announcer.AnnounceFailedConfigDoesNotExist(
selectedFeatureClass,
selectedConfigName);
return;
}
currentAutoEnabledConfig = configClass.static.GetAutoEnabledConfig();
if (selectedConfigName == none && currentAutoEnabledConfig == none) {
announcer.AnnounceFailedAlreadyNoAutoEnabled(selectedFeatureClass);
}
else if (selectedConfigName != none &&
selectedConfigName.Compare(currentAutoEnabledConfig, SCASE_INSENSITIVE))
{
announcer.AnnounceFailedAlreadySameAutoEnabled(
selectedFeatureClass,
selectedConfigName);
}
else
{
configClass.static.SetAutoEnabledConfig(selectedConfigName);
if (selectedConfigName != none)
{
announcer.AnnounceAutoEnabledConfig(
selectedFeatureClass,
selectedConfigName);
}
else {
announcer.AnnounceRemovedAutoEnabledConfig(selectedFeatureClass);
}
}
_.memory.Free(currentAutoEnabledConfig);
}
private function HashTable GetCurrentConfigData(BaseText configName)
{
local class<FeatureConfig> configClass;
if (configName == none) {
return none;
}
configClass = selectedFeatureClass.default.configClass;
if (configClass == none)
{
announcer.AnnounceFailedNoConfigClass(selectedFeatureClass);
return none;
}
return configClass.static.LoadData(configName);
}
private function HashTable GetCurrentSelectedConfigData()
{
return GetCurrentConfigData(selectedConfigName);
}
defaultproperties

388
sources/Commands/ACommandFeature_Announcer.uc

@ -20,25 +20,57 @@
class ACommandFeature_Announcer extends CommandAnnouncer;
var private AnnouncementVariations enabledFeature, disabledFeature;
var private AnnouncementVariations swappedConfig;
var private AnnouncementVariations swappedConfig, pendingConfigSaved;
var private AnnouncementVariations showCurrentConfig, showPendingConfig;
var private AnnouncementVariations configCreated, configRemoved, configEdited;
var private AnnouncementVariations configEditedNew;
var private AnnouncementVariations pendingConfigSavedPublic;
var private AnnouncementVariations pendingConfigSavedPrivate;
var private AnnouncementVariations autoEnabled, removedAutoEnabled;
var private AnnouncementVariations failedAlreadyNoAutoEnabled;
var private AnnouncementVariations failedAlreadySameAutoEnabled;
var private AnnouncementVariations failedConfigAlreadyExists;
var private AnnouncementVariations failedConfigDoesNotExists;
var private AnnouncementVariations failedToLoadFeatureClass;
var private AnnouncementVariations failedNoConfigProvided, failedConfigMissing;
var private AnnouncementVariations failedCannotEnableFeature;
var private AnnouncementVariations failedNoConfigClass;
var private AnnouncementVariations failedNoConfigClass, failedBadConfigName;
var private AnnouncementVariations failedAlreadyEnabled, failedAlreadyDisabled;
var private AnnouncementVariations failedNoDataForConfig, failedExpectedObject;
var private AnnouncementVariations failedBadPointer, failedPendingConfigMissing;
protected function Finalizer()
{
FreeVariations(enabledFeature);
FreeVariations(disabledFeature);
FreeVariations(swappedConfig);
FreeVariations(pendingConfigSaved);
FreeVariations(showCurrentConfig);
FreeVariations(showPendingConfig);
FreeVariations(configCreated);
FreeVariations(configRemoved);
FreeVariations(configEdited);
FreeVariations(configEditedNew);
FreeVariations(pendingConfigSavedPublic);
FreeVariations(pendingConfigSavedPrivate);
FreeVariations(autoEnabled);
FreeVariations(removedAutoEnabled);
FreeVariations(failedAlreadyNoAutoEnabled);
FreeVariations(failedAlreadySameAutoEnabled);
FreeVariations(failedConfigAlreadyExists);
FreeVariations(failedConfigDoesNotExists);
FreeVariations(failedToLoadFeatureClass);
FreeVariations(failedNoConfigProvided);
FreeVariations(failedConfigMissing);
FreeVariations(failedCannotEnableFeature);
FreeVariations(failedNoConfigClass);
FreeVariations(failedBadConfigName);
FreeVariations(failedAlreadyEnabled);
FreeVariations(failedAlreadyDisabled);
FreeVariations(failedNoDataForConfig);
FreeVariations(failedExpectedObject);
FreeVariations(failedBadPointer);
FreeVariations(failedPendingConfigMissing);
super.Finalizer();
}
@ -117,6 +149,268 @@ public final function AnnounceSwappedConfig(
MakeAnnouncement(swappedConfig);
}
public final function AnnouncePublicPendingConfigSaved(
class<Feature> featureClass)
{
local int i;
local array<TextTemplate> templates;
if (!pendingConfigSavedPublic.initialized)
{
pendingConfigSavedPublic.initialized = true;
pendingConfigSavedPublic.toSelfReport = _.text.MakeTemplate_S(
"Active config for feature {$TextEmphasis `%1`} was"
@ "{$TextNeutral modified}");
pendingConfigSavedPublic.toSelfPublic = _.text.MakeTemplate_S(
"%%instigator%% {$TextNeutral modified} active config for feature"
@ "{$TextEmphasis `%1`}");
}
templates = MakeArray(pendingConfigSavedPublic);
for (i = 0; i < templates.length; i += 1) {
templates[i].Reset().ArgClass(featureClass);
}
MakeAnnouncement(pendingConfigSavedPublic);
}
public final function AnnouncePrivatePendingConfigSaved(
class<Feature> featureClass)
{
if (!pendingConfigSavedPrivate.initialized)
{
pendingConfigSavedPrivate.initialized = true;
pendingConfigSavedPrivate.toSelfReport = _.text.MakeTemplate_S(
"Active config for feature {$TextEmphasis `%1`} was"
@ "{$TextNeutral modified}");
}
pendingConfigSavedPrivate.toSelfReport
.Reset()
.ArgClass(featureClass);
MakeAnnouncement(pendingConfigSavedPrivate);
}
public final function AnnounceCurrentConfig(
class<Feature> featureClass,
BaseText config)
{
if (!showCurrentConfig.initialized)
{
showCurrentConfig.initialized = true;
showCurrentConfig.toSelfReport = _.text.MakeTemplate_S(
"Current config \"%2\" for feature {$TextEmphasis `%1`}:");
}
showCurrentConfig.toSelfReport
.Reset()
.ArgClass(featureClass)
.Arg(config);
MakeAnnouncement(showCurrentConfig);
}
public final function AnnouncePendingConfig(
class<Feature> featureClass,
BaseText config)
{
if (!showPendingConfig.initialized)
{
showPendingConfig.initialized = true;
showPendingConfig.toSelfReport = _.text.MakeTemplate_S(
"Pending config \"%2\" for feature {$TextEmphasis `%1`}:");
}
showPendingConfig.toSelfReport
.Reset()
.ArgClass(featureClass)
.Arg(config);
MakeAnnouncement(showPendingConfig);
}
public final function AnnounceConfigCreated(
class<Feature> featureClass,
BaseText config)
{
if (!configCreated.initialized)
{
configCreated.initialized = true;
configCreated.toSelfReport = _.text.MakeTemplate_S(
"{$TextPositive Created config} \"%2\" for feature"
@ "{$TextEmphasis `%1`}");
}
configCreated.toSelfReport
.Reset()
.ArgClass(featureClass)
.Arg(config);
MakeAnnouncement(configCreated);
}
public final function AnnounceConfigRemoved(
class<Feature> featureClass,
BaseText config)
{
if (!configRemoved.initialized)
{
configRemoved.initialized = true;
configRemoved.toSelfReport = _.text.MakeTemplate_S(
"{$TextNegative Removed config} \"%2\" for feature"
@ "{$TextEmphasis `%1`}");
}
configRemoved.toSelfReport
.Reset()
.ArgClass(featureClass)
.Arg(config);
MakeAnnouncement(configRemoved);
}
public final function AnnounceConfigEdited(
class<Feature> featureClass,
BaseText config,
BaseText pathToValue,
BaseText oldValue,
BaseText newValue)
{
if (!configEdited.initialized)
{
configEdited.initialized = true;
configEdited.toSelfReport = _.text.MakeTemplate_S(
"{$TextNeutral Edited config} \"%2\" for feature"
@ "{$TextEmphasis `%1`} by replacing old value %4 at \"%3\""
@ "with new value %5");
}
configEdited.toSelfReport
.Reset()
.ArgClass(featureClass)
.Arg(config)
.Arg(pathToValue)
.Arg(oldValue)
.Arg(newValue);
MakeAnnouncement(configEdited);
}
public final function AnnounceConfigNewValue(
class<Feature> featureClass,
BaseText config,
BaseText pathToValue,
BaseText newValue)
{
if (!configEditedNew.initialized)
{
configEditedNew.initialized = true;
configEditedNew.toSelfReport = _.text.MakeTemplate_S(
"{$TextNeutral Edited config} \"%2\" for feature"
@ "{$TextEmphasis `%1`} by adding value %4 at \"%3\"");
}
configEditedNew.toSelfReport
.Reset()
.ArgClass(featureClass)
.Arg(config)
.Arg(pathToValue)
.Arg(newValue);
MakeAnnouncement(configEditedNew);
}
public final function AnnounceAutoEnabledConfig(
class<Feature> featureClass,
BaseText config)
{
if (!autoEnabled.initialized)
{
autoEnabled.initialized = true;
autoEnabled.toSelfReport = _.text.MakeTemplate_S(
"Config \"%2\" for feature {$TextEmphasis `%1`} will now be"
@ "{$TextPositive auto-enabled}!");
}
autoEnabled.toSelfReport
.Reset()
.ArgClass(featureClass)
.Arg(config);
MakeAnnouncement(autoEnabled);
}
public final function AnnounceRemovedAutoEnabledConfig(
class<Feature> featureClass)
{
if (!removedAutoEnabled.initialized)
{
removedAutoEnabled.initialized = true;
removedAutoEnabled.toSelfReport = _.text.MakeTemplate_S(
"No config for feature {$TextEmphasis `%1`} will now be"
@ "{$TextPositive auto-enabled}!");
}
removedAutoEnabled.toSelfReport
.Reset()
.ArgClass(featureClass);
MakeAnnouncement(removedAutoEnabled);
}
public final function AnnounceFailedAlreadyNoAutoEnabled(
class<Feature> featureClass)
{
if (!failedAlreadyNoAutoEnabled.initialized)
{
failedAlreadyNoAutoEnabled.initialized = true;
failedAlreadyNoAutoEnabled.toSelfReport = _.text.MakeTemplate_S(
"{$TextFailure Cannot remove} auto-enabled config status for"
@ "feature {$TextEmphasis `%1`}: it already has"
@ "{$TextNeutral no auto-enabled config}!");
}
failedAlreadyNoAutoEnabled.toSelfReport
.Reset()
.ArgClass(featureClass);
MakeAnnouncement(failedAlreadyNoAutoEnabled);
}
public final function AnnounceFailedAlreadySameAutoEnabled(
class<Feature> featureClass,
BaseText config)
{
if (!failedAlreadySameAutoEnabled.initialized)
{
failedAlreadySameAutoEnabled.initialized = true;
failedAlreadySameAutoEnabled.toSelfReport = _.text.MakeTemplate_S(
"{$TextFailure Cannot make} config \"%2\" auto-enabled for feature"
@ "{$TextEmphasis `%1`}: it already"
@ "{$TextNeutral is auto-enabled}!");
}
failedAlreadySameAutoEnabled.toSelfReport
.Reset()
.ArgClass(featureClass)
.Arg(config);
MakeAnnouncement(failedAlreadySameAutoEnabled);
}
public final function AnnounceFailedConfigAlreadyExists(
class<Feature> featureClass,
BaseText config)
{
if (!failedConfigAlreadyExists.initialized)
{
failedConfigAlreadyExists.initialized = true;
failedConfigAlreadyExists.toSelfReport = _.text.MakeTemplate_S(
"Config \"%2\" for feature {$TextEmphasis `%1`}"
@ "{$TextFailure already exists}");
}
failedConfigAlreadyExists.toSelfReport
.Reset()
.ArgClass(featureClass)
.Arg(config);
MakeAnnouncement(failedConfigAlreadyExists);
}
public final function AnnounceFailedConfigDoesNotExist(
class<Feature> featureClass,
BaseText config)
{
if (!failedConfigDoesNotExists.initialized)
{
failedConfigDoesNotExists.initialized = true;
failedConfigDoesNotExists.toSelfReport = _.text.MakeTemplate_S(
"Config \"%2\" for feature {$TextEmphasis `%1`}"
@ "{$TextFailure doesn't exist}");
}
failedConfigDoesNotExists.toSelfReport
.Reset()
.ArgClass(featureClass)
.Arg(config);
MakeAnnouncement(failedConfigDoesNotExists);
}
public final function AnnounceFailedToLoadFeatureClass(BaseText failedClassName)
{
if (!failedToLoadFeatureClass.initialized)
@ -136,7 +430,7 @@ public final function AnnounceFailedNoConfigProvided(
{
failedNoConfigProvided.initialized = true;
failedNoConfigProvided.toSelfReport = _.text.MakeTemplate_S(
"{$TextFailue No config specified} and {$TextFailure no"
"{$TextFailure No config specified} and {$TextFailure no"
@ "auto-enabled config} exists for feature {$TextEmphasis `%1`}");
}
failedNoConfigProvided.toSelfReport.Reset().ArgClass(featureClass);
@ -149,12 +443,25 @@ public final function AnnounceFailedConfigMissing(BaseText config)
{
failedConfigMissing.initialized = true;
failedConfigMissing.toSelfReport = _.text.MakeTemplate_S(
"Specified config \"%1\" {$TextFailue doesn't exist}");
"Specified config \"%1\" {$TextFailure doesn't exist}");
}
failedConfigMissing.toSelfReport.Reset().Arg(config);
MakeAnnouncement(failedConfigMissing);
}
public final function AnnounceFailedPendingConfigMissing(BaseText config)
{
if (!failedPendingConfigMissing.initialized)
{
failedPendingConfigMissing.initialized = true;
failedPendingConfigMissing.toSelfReport = _.text.MakeTemplate_S(
"Specified config \"%1\" {$TextFailure doesn't have} any pending"
@ "changes");
}
failedPendingConfigMissing.toSelfReport.Reset().Arg(config);
MakeAnnouncement(failedPendingConfigMissing);
}
public final function AnnounceFailedCannotEnableFeature(
class<Feature> featureClass,
BaseText config)
@ -180,14 +487,26 @@ public final function AnnounceFailedNoConfigClass(
{
failedNoConfigClass.initialized = true;
failedNoConfigClass.toSelfReport = _.text.MakeTemplate_S(
"Feature {$TextEmphasis `%1`} {$TextFailure does not have config"
@ "class}! This is most likely caused by its faulty"
"Feature {$TextEmphasis `%1`} {$TextFailure does not have} config"
@ "class! This is most likely caused by its faulty"
@ "implementation");
}
failedNoConfigClass.toSelfReport.Reset().ArgClass(featureClass);
MakeAnnouncement(failedNoConfigClass);
}
public final function AnnounceFailedBadConfigName(BaseText configName)
{
if (!failedBadConfigName.initialized)
{
failedBadConfigName.initialized = true;
failedBadConfigName.toSelfReport = _.text.MakeTemplate_S(
"{$TextFailure Cannot create} a config with invalid name \"%1\"");
}
failedBadConfigName.toSelfReport.Reset().Arg(configName);
MakeAnnouncement(failedBadConfigName);
}
public final function AnnounceFailedAlreadyDisabled(
class<Feature> featureClass)
{
@ -195,7 +514,8 @@ public final function AnnounceFailedAlreadyDisabled(
{
failedAlreadyDisabled.initialized = true;
failedAlreadyDisabled.toSelfReport = _.text.MakeTemplate_S(
"Feature {$TextEmphasis `%1`} is already {$TextNegative disabled}");
"{$TextFailure Cannot disable} feature {$TextEmphasis `%1`}: it is"
@ "already {$TextNegative disabled}");
}
failedAlreadyDisabled.toSelfReport.Reset().ArgClass(featureClass);
MakeAnnouncement(failedAlreadyDisabled);
@ -209,8 +529,8 @@ public final function AnnounceFailedAlreadyEnabled(
{
failedAlreadyEnabled.initialized = true;
failedAlreadyEnabled.toSelfReport = _.text.MakeTemplate_S(
"Feature {$TextEmphasis `%1`} is already {$TextNegative enabled}"
@ "with specified config \"%2\"");
"{$TextFailure Cannot enable} feature {$TextEmphasis `%1`}: it is"
@ "already {$TextPositive enabled} with specified config \"%2\"");
}
failedAlreadyEnabled.toSelfReport
.Reset()
@ -219,6 +539,56 @@ public final function AnnounceFailedAlreadyEnabled(
MakeAnnouncement(failedAlreadyEnabled);
}
public final function AnnounceFailedNoDataForConfig(
class<Feature> featureClass,
BaseText config)
{
if (!failedNoDataForConfig.initialized)
{
failedNoDataForConfig.initialized = true;
failedNoDataForConfig.toSelfReport = _.text.MakeTemplate_S(
"Feature {$TextEmphasis `%1`} is {$TextFailure missing data} for"
@ "config \"%2\"");
}
failedNoDataForConfig.toSelfReport
.Reset()
.ArgClass(featureClass)
.Arg(config);
MakeAnnouncement(failedNoDataForConfig);
}
public final function AnnounceFailedExpectedObject()
{
if (!failedExpectedObject.initialized)
{
failedExpectedObject.initialized = true;
failedExpectedObject.toSelfReport = _.text.MakeTemplate_S(
"Value change {$TextFailure failed}, because when changing"
@ "the value of the whole config, a JSON object must be provided");
}
MakeAnnouncement(failedExpectedObject);
}
public final function AnnounceFailedBadPointer(
class<Feature> featureClass,
BaseText config,
BaseText pointer)
{
if (!failedBadPointer.initialized)
{
failedBadPointer.initialized = true;
failedBadPointer.toSelfReport = _.text.MakeTemplate_S(
"Provided JSON pointer \"%3\" is {$TextFailure invalid} for config"
@ "\"%2\" of feature {$TextEmphasis `%1`}");
}
failedBadPointer.toSelfReport
.Reset()
.ArgClass(featureClass)
.Arg(config)
.Arg(pointer);
MakeAnnouncement(failedBadPointer);
}
defaultproperties
{
}

353
sources/Tools/PendingConfigsTool.uc

@ -0,0 +1,353 @@
/**
* Auxiliary object for `ACommandFeature` to help with managing pending
* configs for `Feature`s. Pending configs are `HashTable`s with config data
* that are yet to be applied to configs and `Feature`s. They allow users to
* make several changes to the data before actually applying changes to
* the gameplay.
* Copyright 2022 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class PendingConfigsTool extends AcediaObject;
/**
* This tool works by selecting feature (by class) and config (by `Text`
* name) on which it will operate with `SelectConfig()` method and then
* invoking the rest of its methods on these selections.
* There are some expections (`HasPendingConfigFor()` method) that
* explicitly take these values as parameters.
* This tool is supposed to be created once for the "feature" command and
* have its `SelectConfig()` called each execution with user-specified
* parameters.
*/
var private class<Feature> selectedFeatureClass;
var private Text selectedConfigName;
// Possible errors that might occur when working with pending configs
enum PendingConfigToolResult
{
// No error
PCTE_None,
// Pending version of specified config does not exist
PCTE_ConfigMissing,
// JSON object (`HashTable`) was expected as a parameter for the operation,
// but something else was given
PCTE_ExpectedObject,
// Specified JSON pointer points an non-existent location
PCTE_BadPointer
};
struct PendingConfigs
{
var class<Feature> featureClass;
var HashTable pendingSaves;
};
var private array<PendingConfigs> featurePendingEdits;
protected function Finalizer()
{
local int i;
for (i = 0; i < featurePendingEdits.length; i ++) {
_.memory.Free(featurePendingEdits[i].pendingSaves);
}
featurePendingEdits.length = 0;
}
/**
* Selects feature and config to perform all future operations on.
*
* @param featureClass Class of the feature for which to edit pending
* configs.
* @param configName Name of the pending config that caller tool will
* work with.
*/
public final function SelectConfig(
class<Feature> featureClass,
BaseText configName)
{
_.memory.Free(selectedConfigName);
selectedFeatureClass = featureClass;
selectedConfigName = none;
if (configName != none) {
selectedConfigName = configName.LowerCopy();
}
}
/**
* Checks wither caller tool has recorded pending config named `configName`
* for `Feature` defined by class `featureClass`.
* This method does no checks regarding existence of an actual config for
* the specified `Feature` - it only checks whether caller tool has pending
* version.
*
* @param featureClass Class of the `Feature` to check for pending config.
* @param configName Name of the config to check for existence of its
* pending version.
* @return `true` if specified pending config exists and `false` otherwise.
*/
public function bool HasPendingConfigFor(
class<Feature> featureClass,
BaseText configName)
{
local int i;
local bool result;
local Text lowerCaseConfigName;
if (featureClass == none) return false;
if (configName == none) return false;
for (i = 0; i < featurePendingEdits.length; i ++)
{
if (featurePendingEdits[i].featureClass == featureClass)
{
lowerCaseConfigName = configName.LowerCopy();
result = featurePendingEdits[i].pendingSaves
.HasKey(lowerCaseConfigName);
lowerCaseConfigName.FreeSelf();
return result;
}
}
return false;
}
/**
* Returns data recorded for the selected pending config inside caller tool.
*
* @param createIfMissing Method only returns data of the pending version of
* the config and if selected config does not yet have a pending version,
* it will, by default, return `none`. This parameter allows this method to
* create a pending config, based on current config with selected name
* (if it exists).
* @return Data recorded for the selected pending config. If selected config
* does not have a pending version, `createIfMissing` is set to `false`
* or not even current config with selected name exists - method returns
* `none`.
*/
public function HashTable GetPendingConfigData(optional bool createIfMissing)
{
local int editsIndex;
local HashTable result;
local PendingConfigs newRecord;
if (selectedConfigName == none) {
return none;
}
editsIndex = GetPendingConfigDataIndex();
if (editsIndex >= 0)
{
result = featurePendingEdits[editsIndex]
.pendingSaves
.GetHashTable(selectedConfigName);
if (result != none) {
return result;
}
}
if (createIfMissing)
{
if (editsIndex < 0)
{
editsIndex = featurePendingEdits.length;
newRecord.featureClass = selectedFeatureClass;
newRecord.pendingSaves = _.collections.EmptyHashTable();
featurePendingEdits[editsIndex] = newRecord;
}
result = GetCurrentConfigData();
if (result != none)
{
featurePendingEdits[editsIndex]
.pendingSaves
.SetItem(selectedConfigName, result);
}
}
return result;
}
/**
* Makes and edit to the config.
*
* @param pathToValue JSON path at which to make a change.
* @param newValue Value to record at the specified path.
* @return Result of the operation that reports any errors that have occured.
* Any changes are made iff result is `PCTE_None`.
*/
public function PendingConfigToolResult EditConfig(
BaseText pathToValue,
BaseText newValue)
{
local HashTable pendingData;
local JSONPointer pointer;
local Parser parser;
local AcediaObject newValueAsJSON;
local PendingConfigToolResult result;
if (pathToValue == none) {
return PCTE_BadPointer;
}
pendingData = GetPendingConfigData(true);
if (pendingData == none) {
return PCTE_ConfigMissing;
}
// Get guaranteed not-`none` JSON value, treating it as JSON string
// if necessary
parser = _.text.Parse(newValue);
newValueAsJSON = _.json.ParseWith(parser);
parser.FreeSelf();
if (newValueAsJSON == none && newValue != none) {
newValueAsJSON = newValue.Copy();
}
// Set new data
pointer = _.json.Pointer(pathToValue);
result = SetItemByJSON(pendingData, pointer, newValueAsJSON);
pointer.FreeSelf();
pendingData.FreeSelf();
_.memory.Free(newValueAsJSON);
return result;
}
private function PendingConfigToolResult SetItemByJSON(
HashTable data,
JSONPointer pointer,
AcediaObject jsonValue)
{
local Text containerIndex;
local AcediaObject container;
local PendingConfigToolResult result;
if (pointer.IsEmpty())
{
if (HashTable(jsonValue) != none)
{
result = ChangePendingConfigData(HashTable(jsonValue));
_.memory.Free(jsonValue);
return result;
}
_.memory.Free(jsonValue);
return PCTE_ExpectedObject;
}
// Since `!pointer.IsEmpty()`, we are guaranteed to pop a valid value
containerIndex = pointer.Pop();
container = data.GetItemByJSON(pointer);
if (container == none)
{
containerIndex.FreeSelf();
return PCTE_BadPointer;
}
result = SetContainerItemByText(container, containerIndex, jsonValue);
containerIndex.FreeSelf();
container.FreeSelf();
return result;
}
private function PendingConfigToolResult SetContainerItemByText(
AcediaObject container,
BaseText containerIndex,
AcediaObject jsonValue)
{
local int arrayIndex;
local Parser parser;
local ArrayList arrayListContainer;
local HashTable hashTableContainer;
hashTableContainer = HashTable(container);
arrayListContainer = ArrayList(container);
if (hashTableContainer != none) {
hashTableContainer.SetItem(containerIndex, jsonValue);
}
if (arrayListContainer != none)
{
parser = containerIndex.Parse();
if (parser.MInteger(arrayIndex, 10).Ok())
{
arrayListContainer.SetItem(arrayIndex, jsonValue);
parser.FreeSelf();
return PCTE_None;
}
parser.FreeSelf();
if (containerIndex.Compare(P("-"))) {
arrayListContainer.AddItem(jsonValue);
}
else {
return PCTE_BadPointer;
}
}
return PCTE_None;
}
/**
* Removes selected pending config.
*
* @return Result of the operation that reports any errors that have occured.
* Any changes are made iff result is `PCTE_None`.
*/
public final function PendingConfigToolResult RemoveConfig()
{
local int editIndex;
local HashTable pendingSaves;
editIndex = GetPendingConfigDataIndex();
if (editIndex < 0) return PCTE_ConfigMissing;
pendingSaves = featurePendingEdits[editIndex].pendingSaves;
if (!pendingSaves.HasKey(selectedConfigName)) return PCTE_ConfigMissing;
pendingSaves.RemoveItem(selectedConfigName);
if (pendingSaves.GetLength() <= 0)
{
pendingSaves.FreeSelf();
featurePendingEdits.Remove(editIndex, 1);
}
return PCTE_None;
}
private function int GetPendingConfigDataIndex()
{
local int i;
for (i = 0; i < featurePendingEdits.length; i ++)
{
if (featurePendingEdits[i].featureClass == selectedFeatureClass) {
return i;
}
}
return -1;
}
private function PendingConfigToolResult ChangePendingConfigData(
HashTable newData)
{
local int editsIndex;
if (selectedConfigName == none) {
return PCTE_None;
}
editsIndex = GetPendingConfigDataIndex();
if (editsIndex < 0) {
return PCTE_ConfigMissing;
}
featurePendingEdits[editsIndex].pendingSaves
.SetItem(selectedConfigName, newData);
return PCTE_None;
}
private function HashTable GetCurrentConfigData()
{
return selectedFeatureClass.default.configClass.static
.LoadData(selectedConfigName);
}
defaultproperties
{
}
Loading…
Cancel
Save