Browse Source

Optimized the walking of the managed AST.

Signed-off-by: Dimitar Dobrev <dpldobrev@protonmail.com>
pull/1142/head
Dimitar Dobrev 7 years ago committed by João Matos
parent
commit
8427ff8e92
  1. 4
      src/AST/ClassExtensions.cs
  2. 67
      src/AST/DeclIterator.cs
  3. 145
      src/AST/DeclarationsList.cs
  4. 74
      src/AST/Namespace.cs
  5. 4
      src/Generator.Tests/AST/TestAST.cs
  6. 3
      src/Generator/Driver.cs
  7. 2
      src/Generator/Library.cs
  8. 4
      src/Generator/Passes/GenerateAbstractImplementationsPass.cs
  9. 2
      src/Generator/Passes/HandleDefaultParamValuesPass.cs
  10. 3
      src/Generator/Passes/MarkUsedClassInternalsPass.cs
  11. 2
      src/Generator/Passes/MultipleInheritancePass.cs
  12. 2
      src/Generator/Passes/ResolveIncompleteDeclsPass.cs
  13. 18
      src/Generator/Passes/SortDeclarationsPass.cs
  14. 16
      src/Parser/ASTConverter.cs

4
src/AST/ClassExtensions.cs

@ -184,13 +184,13 @@ namespace CppSharp.AST
Class @interface = null; Class @interface = null;
if (specialization == null) if (specialization == null)
{ {
@interface = @class.Namespace.Classes.Find( @interface = @class.Namespace.Classes.FirstOrDefault(
c => c.OriginalClass == @class && c.IsInterface); c => c.OriginalClass == @class && c.IsInterface);
} }
else else
{ {
Class template = specialization.TemplatedDecl.TemplatedClass; Class template = specialization.TemplatedDecl.TemplatedClass;
Class templatedInterface = @class.Namespace.Classes.Find( Class templatedInterface = @class.Namespace.Classes.FirstOrDefault(
c => c.OriginalClass == template && c.IsInterface); c => c.OriginalClass == template && c.IsInterface);
if (templatedInterface != null) if (templatedInterface != null)
@interface = templatedInterface.Specializations.FirstOrDefault( @interface = templatedInterface.Specializations.FirstOrDefault(

67
src/AST/DeclIterator.cs

@ -1,67 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace CppSharp.AST
{
public struct DeclIterator<T> : IEnumerable<T> where T : Declaration
{
private readonly List<Declaration> Declarations;
public DeclIterator(List<Declaration> declarations)
{
Declarations = declarations;
}
public int Count
{
get { return Declarations.OfType<T>().ToArray().Length; }
}
public T this[int index]
{
get { return Declarations.OfType<T>().ToArray()[index]; }
}
public void Add(T declaration)
{
Declarations.Add(declaration);
}
public void AddRange(IEnumerable<T> range)
{
Declarations.AddRange(range);
}
public T Find(Func<T, bool> predicate)
{
return Declarations.OfType<T>().SingleOrDefault<T>(predicate);
}
public int FindIndex(Predicate<T> predicate)
{
return Declarations.OfType<T>().ToList().FindIndex(predicate);
}
public bool Exists(Func<T, bool> predicate)
{
return Declarations.OfType<T>().Any(predicate);
}
public IEnumerator<T> GetEnumerator()
{
return Declarations.OfType<T>().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Replace(T decl, T newDecl)
{
Declarations[Declarations.FindIndex(d => d == decl)] = newDecl;
}
}
}

145
src/AST/DeclarationsList.cs

@ -0,0 +1,145 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace CppSharp.AST
{
public class DeclarationsList : ObservableCollection<Declaration>
{
public IEnumerable<Namespace> Namespaces => OfType<Namespace>(Kind.Namespace);
public IEnumerable<Enumeration> Enums => OfType<Enumeration>(Kind.Enum);
public IEnumerable<Function> Functions => OfType<Function>(Kind.Function);
public IEnumerable<Class> Classes => OfType<Class>(Kind.Class);
public IEnumerable<Template> Templates => OfType<Template>(Kind.Template);
public IEnumerable<TypedefNameDecl> Typedefs => OfType<TypedefNameDecl>(Kind.Typedef);
public IEnumerable<Variable> Variables => OfType<Variable>(Kind.Variable);
public IEnumerable<Event> Events => OfType<Event>(Kind.Event);
public void AddRange(IEnumerable<Declaration> declarations)
{
foreach (Declaration declaration in declarations)
{
Add(declaration);
}
}
protected override void InsertItem(int index, Declaration item)
{
Kind kind = GetKind(item);
int offset = GetOffset(kind);
if (GetStart(kind) < index && index < offset)
{
base.InsertItem(index, item);
}
else
{
base.InsertItem(offset, item);
}
for (Kind i = kind; i <= Kind.Event; i++)
{
if (offsets.ContainsKey(i))
{
offsets[i]++;
}
}
}
protected override void RemoveItem(int index)
{
base.RemoveItem(index);
for (Kind i = Kind.Namespace; i <= Kind.Event; i++)
{
if (offsets.ContainsKey(i) && index < offsets[i])
{
offsets[i]--;
}
}
}
private IEnumerable<T> OfType<T>(Kind kind) where T : Declaration
{
if (!offsets.ContainsKey(kind))
{
yield break;
}
int offset = offsets[kind];
for (int i = GetStart(kind); i < offset; i++)
{
yield return (T) this[i];
}
}
private static Kind GetKind(Declaration item)
{
if (item is Namespace)
return Kind.Namespace;
if (item is Enumeration)
return Kind.Enum;
if (item is Function)
return Kind.Function;
if (item is Class)
return Kind.Class;
if (item is Template)
return Kind.Template;
if (item is TypedefNameDecl)
return Kind.Typedef;
if (item is Variable)
return Kind.Variable;
if (item is Friend)
return Kind.Friend;
if (item is Event)
return Kind.Event;
throw new System.ArgumentOutOfRangeException(nameof(item),
"Unsupported type of declaration.");
}
private int GetOffset(Kind kind)
{
if (!offsets.ContainsKey(kind))
{
for (Kind i = kind - 1; i >= Kind.Namespace; i--)
{
if (offsets.ContainsKey(i))
{
return offsets[kind] = offsets[i];
}
}
offsets[kind] = 0;
}
return offsets[kind];
}
private int GetStart(Kind kind)
{
for (Kind i = kind - 1; i >= Kind.Namespace; i--)
{
if (offsets.ContainsKey(i))
{
return offsets[i];
}
}
return 0;
}
private Dictionary<Kind, int> offsets = new Dictionary<Kind, int>();
private enum Kind
{
Namespace,
Enum,
Function,
Class,
Template,
Typedef,
Variable,
Friend,
Event
}
}
}

74
src/AST/Namespace.cs

@ -11,48 +11,24 @@ namespace CppSharp.AST
{ {
public bool IsAnonymous { get; set; } public bool IsAnonymous { get; set; }
public List<Declaration> Declarations; public DeclarationsList Declarations;
public List<TypeReference> TypeReferences; public List<TypeReference> TypeReferences;
public DeclIterator<Namespace> Namespaces public IEnumerable<Namespace> Namespaces => Declarations.Namespaces;
{
get { return new DeclIterator<Namespace>(Declarations); }
}
public DeclIterator<Enumeration> Enums public IEnumerable<Enumeration> Enums => Declarations.Enums;
{
get { return new DeclIterator<Enumeration>(Declarations); }
}
public DeclIterator<Function> Functions public IEnumerable<Function> Functions => Declarations.Functions;
{
get { return new DeclIterator<Function>(Declarations); }
}
public DeclIterator<Class> Classes public IEnumerable<Class> Classes => Declarations.Classes;
{
get { return new DeclIterator<Class>(Declarations); }
}
public DeclIterator<Template> Templates public IEnumerable<Template> Templates => Declarations.Templates;
{
get { return new DeclIterator<Template>(Declarations); }
}
public DeclIterator<TypedefNameDecl> Typedefs public IEnumerable<TypedefNameDecl> Typedefs => Declarations.Typedefs;
{
get { return new DeclIterator<TypedefNameDecl>(Declarations); }
}
public DeclIterator<Variable> Variables public IEnumerable<Variable> Variables => Declarations.Variables;
{
get { return new DeclIterator<Variable>(Declarations); }
}
public DeclIterator<Event> Events public IEnumerable<Event> Events => Declarations.Events;
{
get { return new DeclIterator<Event>(Declarations); }
}
// Used to keep track of anonymous declarations. // Used to keep track of anonymous declarations.
public Dictionary<ulong, Declaration> Anonymous; public Dictionary<ulong, Declaration> Anonymous;
@ -72,7 +48,7 @@ namespace CppSharp.AST
protected DeclarationContext() protected DeclarationContext()
{ {
Declarations = new List<Declaration>(); Declarations = new DeclarationsList();
TypeReferences = new List<TypeReference>(); TypeReferences = new List<TypeReference>();
Anonymous = new Dictionary<ulong, Declaration>(); Anonymous = new Dictionary<ulong, Declaration>();
} }
@ -121,7 +97,7 @@ namespace CppSharp.AST
foreach (var @namespace in namespaces) foreach (var @namespace in namespaces)
{ {
var childNamespace = currentNamespace.Namespaces.Find( var childNamespace = currentNamespace.Namespaces.FirstOrDefault(
e => e.Name.Equals(@namespace)); e => e.Name.Equals(@namespace));
if (childNamespace == null) if (childNamespace == null)
@ -145,7 +121,7 @@ namespace CppSharp.AST
Namespace = this, Namespace = this,
}; };
Namespaces.Add(@namespace); Declarations.Add(@namespace);
} }
return @namespace; return @namespace;
@ -158,12 +134,12 @@ namespace CppSharp.AST
if (entries.Count <= 1) if (entries.Count <= 1)
{ {
var @enum = Enums.Find(e => e.Name.Equals(name)); var @enum = Enums.FirstOrDefault(e => e.Name.Equals(name));
if (@enum == null && createDecl) if (@enum == null && createDecl)
{ {
@enum = new Enumeration() { Name = name, Namespace = this }; @enum = new Enumeration() { Name = name, Namespace = this };
Enums.Add(@enum); Declarations.Add(@enum);
} }
return @enum; return @enum;
@ -194,12 +170,12 @@ namespace CppSharp.AST
if (entries.Count <= 1) if (entries.Count <= 1)
{ {
var function = Functions.Find(e => e.Name.Equals(name)); var function = Functions.FirstOrDefault(e => e.Name.Equals(name));
if (function == null && createDecl) if (function == null && createDecl)
{ {
function = new Function() { Name = name, Namespace = this }; function = new Function() { Name = name, Namespace = this };
Functions.Add(function); Declarations.Add(function);
} }
return function; return function;
@ -240,7 +216,7 @@ namespace CppSharp.AST
{ {
if (string.IsNullOrEmpty(name)) return null; if (string.IsNullOrEmpty(name)) return null;
var @class = Classes.Find(c => c.Name.Equals(name, stringComparison)) ?? var @class = Classes.FirstOrDefault(c => c.Name.Equals(name, stringComparison)) ??
Namespaces.Select(n => n.FindClass(name, stringComparison)).FirstOrDefault(c => c != null); Namespaces.Select(n => n.FindClass(name, stringComparison)).FirstOrDefault(c => c != null);
if (@class != null) if (@class != null)
return @class.CompleteDeclaration == null ? return @class.CompleteDeclaration == null ?
@ -258,7 +234,7 @@ namespace CppSharp.AST
if (createDecl) if (createDecl)
{ {
@class = CreateClass(name, isComplete); @class = CreateClass(name, isComplete);
Classes.Add(@class); Declarations.Add(@class);
} }
return @class; return @class;
@ -276,7 +252,7 @@ namespace CppSharp.AST
if (@class.IsIncomplete) if (@class.IsIncomplete)
{ {
@class.CompleteDeclaration = newClass; @class.CompleteDeclaration = newClass;
Classes.Replace(@class, newClass); Declarations[Declarations.IndexOf(@class)] = newClass;
} }
return newClass; return newClass;
@ -316,12 +292,12 @@ namespace CppSharp.AST
if (entries.Count <= 1) if (entries.Count <= 1)
{ {
var typeDef = Typedefs.Find(e => e.Name.Equals(name)); var typeDef = Typedefs.FirstOrDefault(e => e.Name.Equals(name));
if (typeDef == null && createDecl) if (typeDef == null && createDecl)
{ {
typeDef = new TypedefDecl { Name = name, Namespace = this }; typeDef = new TypedefDecl { Name = name, Namespace = this };
Typedefs.Add(typeDef); Declarations.Add(typeDef);
} }
return typeDef; return typeDef;
@ -349,7 +325,7 @@ namespace CppSharp.AST
public Enumeration FindEnumWithItem(string name) public Enumeration FindEnumWithItem(string name)
{ {
return Enums.Find(e => e.ItemsByName.ContainsKey(name)) ?? return Enums.FirstOrDefault(e => e.ItemsByName.ContainsKey(name)) ??
(from declContext in Namespaces.Union<DeclarationContext>(Classes) (from declContext in Namespaces.Union<DeclarationContext>(Classes)
let @enum = declContext.FindEnumWithItem(name) let @enum = declContext.FindEnumWithItem(name)
where @enum != null where @enum != null
@ -374,8 +350,8 @@ namespace CppSharp.AST
get get
{ {
Func<Declaration, bool> pred = (t => t.IsGenerated); Func<Declaration, bool> pred = (t => t.IsGenerated);
return Enums.Exists(pred) || HasFunctions || Typedefs.Exists(pred) return Enums.Any(pred) || HasFunctions || Typedefs.Any(pred)
|| Classes.Any() || Namespaces.Exists(n => n.HasDeclarations) || || Classes.Any() || Namespaces.Any(n => n.HasDeclarations) ||
Templates.Any(pred); Templates.Any(pred);
} }
} }
@ -385,7 +361,7 @@ namespace CppSharp.AST
get get
{ {
Func<Declaration, bool> pred = (t => t.IsGenerated); Func<Declaration, bool> pred = (t => t.IsGenerated);
return Functions.Exists(pred) || Namespaces.Exists(n => n.HasFunctions); return Functions.Any(pred) || Namespaces.Any(n => n.HasFunctions);
} }
} }

4
src/Generator.Tests/AST/TestAST.cs

@ -246,7 +246,7 @@ namespace CppSharp.Generator.Tests.AST
{ {
var @class = AstContext.FindClass("TestTemplateFunctions").FirstOrDefault(); var @class = AstContext.FindClass("TestTemplateFunctions").FirstOrDefault();
Assert.IsNotNull(@class, "Couldn't find TestTemplateFunctions class."); Assert.IsNotNull(@class, "Couldn't find TestTemplateFunctions class.");
Assert.AreEqual(6, @class.Templates.Count); Assert.AreEqual(6, @class.Templates.Count());
var twoParamMethodTemplate = @class.Templates.OfType<FunctionTemplate>() var twoParamMethodTemplate = @class.Templates.OfType<FunctionTemplate>()
.FirstOrDefault(t => t.Name == "MethodTemplateWithTwoTypeParameter"); .FirstOrDefault(t => t.Name == "MethodTemplateWithTwoTypeParameter");
Assert.IsNotNull(twoParamMethodTemplate); Assert.IsNotNull(twoParamMethodTemplate);
@ -533,7 +533,7 @@ namespace CppSharp.Generator.Tests.AST
{ {
var template = AstContext.FindDecl<ClassTemplate>("TestTemplateClass").First(); var template = AstContext.FindDecl<ClassTemplate>("TestTemplateClass").First();
var cppTypePrinter = new CppTypePrinter { PrintScopeKind = TypePrintScopeKind.Qualified }; var cppTypePrinter = new CppTypePrinter { PrintScopeKind = TypePrintScopeKind.Qualified };
Assert.That(template.Specializations[3].Classes[0].Visit(cppTypePrinter), Assert.That(template.Specializations[3].Classes.First().Visit(cppTypePrinter),
Is.EqualTo("TestTemplateClass<Math::Complex>::NestedInTemplate")); Is.EqualTo("TestTemplateClass<Math::Complex>::NestedInTemplate"));
} }

3
src/Generator/Driver.cs

@ -212,8 +212,7 @@ namespace CppSharp
public void SetupPasses(ILibrary library) public void SetupPasses(ILibrary library)
{ {
var TranslationUnitPasses = Context.TranslationUnitPasses; var TranslationUnitPasses = Context.TranslationUnitPasses;
TranslationUnitPasses.AddPass(new SortDeclarationsPass());
TranslationUnitPasses.AddPass(new ResolveIncompleteDeclsPass()); TranslationUnitPasses.AddPass(new ResolveIncompleteDeclsPass());
TranslationUnitPasses.AddPass(new IgnoreSystemDeclarationsPass()); TranslationUnitPasses.AddPass(new IgnoreSystemDeclarationsPass());
if (Options.IsCSharpGenerator) if (Options.IsCSharpGenerator)

2
src/Generator/Library.cs

@ -172,7 +172,7 @@ namespace CppSharp
if (@enum.Items.Count > 0) if (@enum.Items.Count > 0)
{ {
unit.Enums.Add(@enum); unit.Declarations.Add(@enum);
break; break;
} }
} }

4
src/Generator/Passes/GenerateAbstractImplementationsPass.cs

@ -24,9 +24,9 @@ namespace CppSharp.Passes
var result = base.VisitTranslationUnit(unit); var result = base.VisitTranslationUnit(unit);
foreach (var internalImpl in internalImpls) foreach (var internalImpl in internalImpls)
if (internalImpl.Namespace != null) if (internalImpl.Namespace != null)
internalImpl.Namespace.Classes.Add(internalImpl); internalImpl.Namespace.Declarations.Add(internalImpl);
else else
unit.Classes.AddRange(internalImpls); unit.Declarations.AddRange(internalImpls);
internalImpls.Clear(); internalImpls.Clear();
return result; return result;

2
src/Generator/Passes/HandleDefaultParamValuesPass.cs

@ -30,7 +30,7 @@ namespace CppSharp.Passes
return false; return false;
var result = base.VisitTranslationUnit(unit); var result = base.VisitTranslationUnit(unit);
foreach (var overload in overloads) foreach (var overload in overloads)
overload.Key.Functions.AddRange(overload.Value); overload.Key.Declarations.AddRange(overload.Value);
overloads.Clear(); overloads.Clear();
return result; return result;
} }

3
src/Generator/Passes/MarkUsedClassInternalsPass.cs

@ -60,7 +60,8 @@ namespace CppSharp.Passes
if (template?.Ignore == true) if (template?.Ignore == true)
template.GenerationKind = GenerationKind.Internal; template.GenerationKind = GenerationKind.Internal;
Class nested = template?.Classes.Find(c => c.OriginalName == decl.OriginalName); Class nested = template?.Classes.FirstOrDefault(
c => c.OriginalName == decl.OriginalName);
if (nested?.Ignore == true) if (nested?.Ignore == true)
nested.GenerationKind = GenerationKind.Internal; nested.GenerationKind = GenerationKind.Internal;

2
src/Generator/Passes/MultipleInheritancePass.cs

@ -144,7 +144,7 @@ namespace CppSharp.Passes
@interface.Methods.Add(dispose); @interface.Methods.Add(dispose);
} }
@interface.Events.AddRange(@base.Events); @interface.Declarations.AddRange(@base.Events);
var type = new QualifiedType(new BuiltinType(PrimitiveType.IntPtr)); var type = new QualifiedType(new BuiltinType(PrimitiveType.IntPtr));
var adjustmentTo = new Property var adjustmentTo = new Property

2
src/Generator/Passes/ResolveIncompleteDeclsPass.cs

@ -39,7 +39,7 @@ namespace CppSharp.Passes
Class templatedClass = template.TemplatedClass; Class templatedClass = template.TemplatedClass;
var parentSpecialization = templatedClass.Namespace as ClassTemplateSpecialization; var parentSpecialization = templatedClass.Namespace as ClassTemplateSpecialization;
if (parentSpecialization != null) if (parentSpecialization != null)
templatedClass = parentSpecialization.TemplatedDecl.TemplatedClass.Classes.Find( templatedClass = parentSpecialization.TemplatedDecl.TemplatedClass.Classes.FirstOrDefault(
c => c.OriginalName == template.OriginalName) ?? template.TemplatedClass; c => c.OriginalName == template.OriginalName) ?? template.TemplatedClass;
// store all specializations in the real template class because ClassTemplateDecl only forwards // store all specializations in the real template class because ClassTemplateDecl only forwards
foreach (var specialization in template.Specializations.Where( foreach (var specialization in template.Specializations.Where(

18
src/Generator/Passes/SortDeclarationsPass.cs

@ -1,18 +0,0 @@
using System.Linq;
using CppSharp.AST;
namespace CppSharp.Passes
{
public class SortDeclarationsPass : TranslationUnitPass
{
public override bool VisitNamespace(Namespace @namespace)
{
if (!base.VisitNamespace(@namespace) || @namespace.Ignore)
return false;
@namespace.Declarations = @namespace.Declarations.OrderBy(
d => d.DefinitionOrder).ToList();
return true;
}
}
}

16
src/Parser/ASTConverter.cs

@ -983,28 +983,28 @@ namespace CppSharp
{ {
var decl = ctx.GetNamespaces(i); var decl = ctx.GetNamespaces(i);
var _decl = Visit(decl) as AST.Namespace; var _decl = Visit(decl) as AST.Namespace;
_ctx.Namespaces.Add(_decl); _ctx.Declarations.Add(_decl);
} }
for (uint i = 0; i < ctx.EnumsCount; ++i) for (uint i = 0; i < ctx.EnumsCount; ++i)
{ {
var decl = ctx.GetEnums(i); var decl = ctx.GetEnums(i);
var _decl = Visit(decl) as AST.Enumeration; var _decl = Visit(decl) as AST.Enumeration;
_ctx.Enums.Add(_decl); _ctx.Declarations.Add(_decl);
} }
for (uint i = 0; i < ctx.FunctionsCount; ++i) for (uint i = 0; i < ctx.FunctionsCount; ++i)
{ {
var decl = ctx.GetFunctions(i); var decl = ctx.GetFunctions(i);
var _decl = Visit(decl) as AST.Function; var _decl = Visit(decl) as AST.Function;
_ctx.Functions.Add(_decl); _ctx.Declarations.Add(_decl);
} }
for (uint i = 0; i < ctx.TemplatesCount; ++i) for (uint i = 0; i < ctx.TemplatesCount; ++i)
{ {
var decl = ctx.GetTemplates(i); var decl = ctx.GetTemplates(i);
var _decl = Visit(decl) as AST.Template; var _decl = Visit(decl) as AST.Template;
_ctx.Templates.Add(_decl); _ctx.Declarations.Add(_decl);
} }
for (uint i = 0; i < ctx.ClassesCount; ++i) for (uint i = 0; i < ctx.ClassesCount; ++i)
@ -1012,28 +1012,28 @@ namespace CppSharp
var decl = ctx.GetClasses(i); var decl = ctx.GetClasses(i);
var _decl = Visit(decl) as AST.Class; var _decl = Visit(decl) as AST.Class;
if (!_decl.IsIncomplete || _decl.IsOpaque) if (!_decl.IsIncomplete || _decl.IsOpaque)
_ctx.Classes.Add(_decl); _ctx.Declarations.Add(_decl);
} }
for (uint i = 0; i < ctx.TypedefsCount; ++i) for (uint i = 0; i < ctx.TypedefsCount; ++i)
{ {
var decl = ctx.GetTypedefs(i); var decl = ctx.GetTypedefs(i);
var _decl = Visit(decl) as AST.TypedefDecl; var _decl = Visit(decl) as AST.TypedefDecl;
_ctx.Typedefs.Add(_decl); _ctx.Declarations.Add(_decl);
} }
for (uint i = 0; i < ctx.TypeAliasesCount; ++i) for (uint i = 0; i < ctx.TypeAliasesCount; ++i)
{ {
var decl = ctx.GetTypeAliases(i); var decl = ctx.GetTypeAliases(i);
var _decl = Visit(decl) as AST.TypeAlias; var _decl = Visit(decl) as AST.TypeAlias;
_ctx.Typedefs.Add(_decl); _ctx.Declarations.Add(_decl);
} }
for (uint i = 0; i < ctx.VariablesCount; ++i) for (uint i = 0; i < ctx.VariablesCount; ++i)
{ {
var decl = ctx.GetVariables(i); var decl = ctx.GetVariables(i);
var _decl = Visit(decl) as AST.Variable; var _decl = Visit(decl) as AST.Variable;
_ctx.Variables.Add(_decl); _ctx.Declarations.Add(_decl);
} }
for (uint i = 0; i < ctx.FriendsCount; ++i) for (uint i = 0; i < ctx.FriendsCount; ++i)

Loading…
Cancel
Save