Skip to content

Commit

Permalink
Speedup context menu by cloning TemplateContext
Browse files Browse the repository at this point in the history
  • Loading branch information
coenm committed Oct 10, 2024
1 parent 70a8887 commit e99887b
Show file tree
Hide file tree
Showing 18 changed files with 472 additions and 342 deletions.
11 changes: 9 additions & 2 deletions src/RepoM.ActionMenu.Core/ActionMenu/Context/EnvScriptObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ internal sealed class EnvScriptObject : IScriptObject
{
private readonly IDictionary<string, string> _env;

public static EnvScriptObject Create()
private static EnvScriptObject Create()
{
var env = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

Expand All @@ -33,6 +33,8 @@ public static EnvScriptObject Create()
return new EnvScriptObject(env);
}

public static EnvScriptObject Instance { get; } = Create();

public EnvScriptObject(IDictionary<string, string> envVars)
{
_env = envVars;
Expand Down Expand Up @@ -80,11 +82,16 @@ public void SetReadOnly(string member, bool readOnly)
// intentionally do nothing
}

public IScriptObject Clone(bool deep)
public EnvScriptObject Clone()
{
return new EnvScriptObject(new Dictionary<string, string>(_env));
}

IScriptObject IScriptObject.Clone(bool deep)
{
return Clone();
}

public int Count => _env.Count;

public bool IsReadOnly
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ public void SetReadOnly(string member, bool readOnly)
// intentionally do nothing
}

public IScriptObject Clone(bool deep)
public EnvSetScriptObject Clone()
{
EnvScriptObject[] items = _stack.Items;
var result = new EnvSetScriptObject((EnvScriptObject)items[0].Clone(true));
var result = new EnvSetScriptObject(items[0].Clone());

if (items.Length <= 1)
{
Expand All @@ -88,7 +88,7 @@ public IScriptObject Clone(bool deep)
{
if (items[i] != null)
{
result.Push((EnvScriptObject)items[i].Clone(true));
result.Push(items[i].Clone());
}
}

Expand Down Expand Up @@ -122,4 +122,9 @@ public void Dispose()

return null;
}

IScriptObject IScriptObject.Clone(bool deep)
{
return Clone();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ protected override async IAsyncEnumerable<UserInterfaceRepositoryActionBase> Map
name,
repository,
context,
captureScope: false,
async ctx => await EnumerateRemotes(ctx.Repository).ConfigureAwait(false))
{
CanExecute = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ namespace RepoM.ActionMenu.Core.ActionMenu.Model.ActionMenus.Folder;
[UsedImplicitly]
internal class RepositoryActionFolderV1Mapper : ActionToRepositoryActionMapperBase<RepositoryActionFolderV1>
{

protected override async IAsyncEnumerable<UserInterfaceRepositoryActionBase> MapAsync(RepositoryActionFolderV1 action, IActionMenuGenerationContext context, IRepository repository)
{
var name = await context.RenderStringAsync(action.Name).ConfigureAwait(false);
Expand All @@ -22,20 +21,14 @@ protected override async IAsyncEnumerable<UserInterfaceRepositoryActionBase> Map
yield break;
}

#pragma warning disable S2583 // Change this condition so that it does not always evaluate to 'False'. Some code paths are unreachable
// Deferred with cloning the context doesn't work yet (https://github.com/coenm/RepoM/issues/85) therefore, set to false.
#pragma warning disable S125
// var isDeferred = await action.IsDeferred.EvaluateAsync(context).ConfigureAwait(false);
#pragma warning restore S125
var isDeferred = false;
var isDeferred = await action.IsDeferred.EvaluateAsync(context).ConfigureAwait(false);

if (isDeferred)
{
yield return new DeferredSubActionsUserInterfaceRepositoryAction(
name,
repository,
context,
action.Actions != null,
async ctx => await ctx.AddActionMenusAsyncArray(action.Actions).ConfigureAwait(false))
{
CanExecute = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ protected override async IAsyncEnumerable<UserInterfaceRepositoryActionBase> Map
name,
repository,
context,
false,
_ =>
Task.FromResult(repository.LocalBranches
.Take(50)
Expand All @@ -42,7 +41,6 @@ protected override async IAsyncEnumerable<UserInterfaceRepositoryActionBase> Map
remoteBranchesTranslated,
repository,
context,
false,
_ =>
{
UserInterfaceRepositoryActionBase[] remoteBranches = repository
Expand Down
91 changes: 59 additions & 32 deletions src/RepoM.ActionMenu.Core/Model/ActionMenuGenerationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@ internal class ActionMenuGenerationContext : TemplateContext, IActionMenuGenerat
{
private readonly ITemplateParser _templateParser;
private readonly ITemplateContextRegistration[] _functionsArray;
private readonly IActionMenuDeserializer _deserializer;
private readonly IActionToRepositoryActionMapper[] _repositoryActionMappers;
private readonly IActionMenuDeserializer _deserializer;
private readonly IActionToRepositoryActionMapper[] _repositoryActionMappers;
private readonly IContextActionProcessor[] _contextActionMappers;
private RepoMScriptObject _rootScriptObject = null!; // used for cloning.
private RepoMScriptObject _rootScriptObject = null!;
private EnvSetScriptObject? _env;
private FastStack<DisposableContextScriptObject> _globals = new(4);
private DisposableContextScriptObject _repositoryActionsScriptContext = null!;

public ActionMenuGenerationContext(
ITemplateParser templateParser,
Expand All @@ -44,15 +47,11 @@ public ActionMenuGenerationContext(
_deserializer = deserializer ?? throw new ArgumentNullException(nameof(deserializer));
_contextActionMappers = contextActionMappers ?? throw new ArgumentNullException(nameof(contextActionMappers));
}

public IFileSystem FileSystem { get; }

public DisposableContextScriptObject RepositoryActionsScriptContext { get; private set; } = null!;

public IRepository Repository { get; private set; } = null!;

private EnvSetScriptObject? _env;
public IRepository Repository { get; private set; } = null!;

public IFileSystem FileSystem { get; }

public EnvSetScriptObject Env => _env ??= (EnvSetScriptObject)_rootScriptObject["env"];

public async Task AddRepositoryContextAsync(Context? reposContext)
Expand All @@ -64,7 +63,7 @@ public async Task AddRepositoryContextAsync(Context? reposContext)

foreach (IContextAction contextAction in reposContext)
{
await RepositoryActionsScriptContext.AddContextActionAsync(contextAction).ConfigureAwait(false);
await _repositoryActionsScriptContext.AddContextActionAsync(contextAction).ConfigureAwait(false);
}
}

Expand All @@ -86,19 +85,20 @@ public async IAsyncEnumerable<UserInterfaceRepositoryActionBase> AddActionMenusA
}
}

public IActionMenuGenerationContext Clone()
public void PushGlobal(DisposableContextScriptObject scriptObject)
{
#pragma warning disable S125
// this method doesn't work yet. Cloning the full Template context is not possible.
// to be implemented (https://github.com/coenm/RepoM/issues/85)
/*
var repoMScriptObject = (RepoMScriptObject)_rootScriptObject.Clone(true);
base.PushGlobal(scriptObject);
_globals.Push(scriptObject);
}

IScriptObject e = ((EnvSetScriptObject)_rootScriptObject["env"]).Clone(true);
repoMScriptObject.SetValue("env", e, false);
*/
#pragma warning restore S125
public new void PopGlobal()
{
_globals.Pop();
base.PopGlobal();
}

public IActionMenuGenerationContext Clone()
{
var result = new ActionMenuGenerationContext(
_templateParser,
FileSystem,
Expand All @@ -107,37 +107,64 @@ public IActionMenuGenerationContext Clone()
_deserializer,
_contextActionMappers);

result.Initialize(Repository);

result.InitializeFrom(this);
return result;
}

internal void Initialize(IRepository repository)
{
Repository = repository ?? throw new ArgumentNullException(nameof(repository));

_rootScriptObject = CreateAndInitRepoMScriptObject(Repository);

_rootScriptObject = CreateAndInitRepoMScriptObject(
new EnvSetScriptObject(EnvScriptObject.Instance));

foreach (ITemplateContextRegistration contextRegistration in _functionsArray)
{
contextRegistration.RegisterFunctions(Decorate<ActionMenuGenerationContext>(_rootScriptObject));
}

PushGlobal(_rootScriptObject);
RepositoryActionsScriptContext = new DisposableContextScriptObject(this, Env, _contextActionMappers);
PushGlobal(RepositoryActionsScriptContext);
_repositoryActionsScriptContext = new DisposableContextScriptObject(this, _contextActionMappers);
PushGlobal(_repositoryActionsScriptContext);
}

private void InitializeFrom(ActionMenuGenerationContext @this)
{
Repository = @this.Repository;

_rootScriptObject = CreateAndInitRepoMScriptObject(@this.Env.Clone());
foreach (ITemplateContextRegistration contextRegistration in _functionsArray)
{
contextRegistration.RegisterFunctions(Decorate<ActionMenuGenerationContext>(_rootScriptObject));
}

PushGlobal(_rootScriptObject);

// -2 because _rootScriptObject and RepositoryActionsScriptContext are already added
if (@this.GlobalCount -2 != @this._globals.Count)
{
throw new Exception();
}

_repositoryActionsScriptContext = new DisposableContextScriptObject(this, _contextActionMappers);
PushGlobal(_repositoryActionsScriptContext);

for (var index = 0; index < @this._globals.Count; index++)
{
@this._globals.Items[index].CloneUsingNewContext(this);
}
}

private static RepoMScriptObject CreateAndInitRepoMScriptObject(IRepository repository)
private RepoMScriptObject CreateAndInitRepoMScriptObject(EnvSetScriptObject env)
{
var scriptObj = new RepoMScriptObject();

scriptObj.Import(typeof(InitialFunctions));

scriptObj.SetValue("file", new FileFunctions(), true);
scriptObj.SetValue("repository", new RepositoryFunctions(repository), true);
scriptObj.SetValue("repository", new RepositoryFunctions(Repository), true);

scriptObj.Add("env", new EnvSetScriptObject(EnvScriptObject.Create()));
scriptObj.Add("env", env);
scriptObj.SetReadOnly("env", false); // this is not what we want, but it's the only way to make it work

return scriptObj;
Expand Down Expand Up @@ -168,7 +195,7 @@ private async Task<IEnumerable<UserInterfaceRepositoryActionBase>> AddMenuAction
}

var items = new List<UserInterfaceRepositoryActionBase>();
await foreach (UserInterfaceRepositoryActionBase item in mapper.MapAsync(menuAction, this, Repository).ConfigureAwait(false))
await foreach (UserInterfaceRepositoryActionBase item in mapper.MapAsync(in menuAction, this, Repository).ConfigureAwait(false))
{
items.Add(item);
}
Expand All @@ -184,7 +211,7 @@ public async Task<string> RenderStringAsync(string text)

private DisposableContextScriptObject PushNewContext()
{
return new DisposableContextScriptObject(this, Env, _contextActionMappers);
return new DisposableContextScriptObject(this, _contextActionMappers);
}

public async Task<object> EvaluateAsync(string? text)
Expand Down
Loading

0 comments on commit e99887b

Please sign in to comment.