<commit_before>﻿namespace Foom.Ecs

open System
open System.Diagnostics
open System.Reflection
open System.Collections.Generic
open System.Collections.Concurrent
open System.Threading.Tasks
open System.Runtime.InteropServices

open Foom.Collections

#nowarn "9"

type IEntityLookupData =

    abstract Entities : Entity UnsafeResizeArray with get

    abstract GetIndex : int -> int

    abstract GetComponent : int -> Component

open Events

[<ReferenceEquality>]
type EntityLookupData<'T when 'T :> Component> =
    {
        ComponentRemovedEvent: Event<ComponentRemoved<'T>>

        ComponentAddedTriggers : ResizeArray<'T -> unit>

        RemoveComponent: Entity -> unit

        IndexLookup: int []
        Entities: Entity UnsafeResizeArray
        Components: 'T UnsafeResizeArray
    }

    interface IEntityLookupData with

        member this.Entities = this.Entities

        member this.GetIndex id = this.IndexLookup.[id]

        member this.GetComponent index = this.Components.Buffer.[index] :> Component

type EntityBuilder = EntityBuilder of (Entity -> EntityManager -> unit)

and [<ReferenceEquality>] EntityManager =
    {
        EventAggregator: EventAggregator

        MaxEntityAmount: int
        Lookup: ConcurrentDictionary<Type, IEntityLookupData>

        ActiveVersions: uint32 []
        ActiveIndices: bool []

        mutable nextEntityIndex: int
        RemovedEntityQueue: Queue<Entity>

        EntityRemovals: ((Entity -> unit) ResizeArray) []

        EntitySpawnedEvent: Event<EntitySpawned>
        EntityDestroyedEvent: Event<EntityDestroyed>

        mutable CurrentIterations: int
        PendingQueue: Queue<unit -> unit>
    }

    static member Create (eventManager: EventAggregator, maxEntityAmount) =
        if maxEntityAmount <= 0 then
            failwith "Max entity amount must be greater than 0."

        let maxEntityAmount = maxEntityAmount + 1
        let lookup = ConcurrentDictionary<Type, IEntityLookupData> ()

        let activeVersions = Array.init maxEntityAmount (fun _ -> 0u)
        let activeIndices = Array.zeroCreate<bool> maxEntityAmount

        let mutable nextEntityIndex = 1
        let removedEntityQueue = Queue<Entity> () 

        let entityRemovals : ((Entity -> unit) ResizeArray) [] = Array.init maxEntityAmount (fun _ -> ResizeArray ())

        let entitySpawnedEvent = eventManager.GetEvent<EntitySpawned> ()
        let entityDestroyedEvent = eventManager.GetEvent<EntityDestroyed> ()

        {
            EventAggregator = eventManager
            MaxEntityAmount = maxEntityAmount
            Lookup = lookup
            ActiveVersions = activeVersions
            ActiveIndices = activeIndices
            nextEntityIndex = nextEntityIndex
            RemovedEntityQueue = removedEntityQueue
            EntityRemovals = entityRemovals
            EntitySpawnedEvent = entitySpawnedEvent
            EntityDestroyedEvent = entityDestroyedEvent
            CurrentIterations = 0
            PendingQueue = Queue ()
        }

    member inline this.IsValidEntity (entity: Entity) =
        not (entity.Index.Equals 0u) && this.ActiveVersions.[entity.Index].Equals entity.Version

    member inline this.ResolvePendingQueues () =
        if this.CurrentIterations = 0 then

            while this.PendingQueue.Count > 0 do
                let action = this.PendingQueue.Dequeue ()
                action ()

    member this.GetEntityLookupData<'T when 'T :> Component> () : EntityLookupData<'T> =
        let t = typeof<'T>
        let mutable data = Unchecked.defaultof<IEntityLookupData>
        match this.Lookup.TryGetValue(t, &data) with
        | true -> data :?> EntityLookupData<'T>
        | _ ->
            let triggers = ResizeArray ()

            let added = this.EventAggregator.GetComponentAddedEvent<'T> ()
            triggers.Add (fun o -> added.Trigger o)

            if t.GetTypeInfo().BaseType <> typeof<Component> then
                let t = t.GetTypeInfo().BaseType
                let mutable trigger = Unchecked.defaultof<obj -> unit>
                if this.EventAggregator.TryGetComponentAddedTrigger (t, &trigger) then
                    triggers.Add (fun o -> trigger o)

                if t.GetTypeInfo().BaseType <> typeof<Component> then
                    let t = t.GetTypeInfo().BaseType
                    let mutable trigger = Unchecked.defaultof<obj -> unit>
                    if this.EventAggregator.TryGetComponentAddedTrigger (t, &trigger) then
                        triggers.Add (fun o -> trigger o)

            let factory t =
                let data =
                    {
                        ComponentRemovedEvent = this.EventAggregator.GetEvent<ComponentRemoved<'T>> ()

                        ComponentAddedTriggers = triggers

                        RemoveComponent = fun entity -> this.Remove<'T> entity

                        IndexLookup = Array.init this.MaxEntityAmount (fun _ -> -1) // -1 means that no component exists for that entity
                        Entities = UnsafeResizeArray.Create this.MaxEntityAmount
                        Components = UnsafeResizeArray.Create this.MaxEntityAmount
                    }

                data :> IEntityLookupData

            this.Lookup.GetOrAdd(t, factory) :?> EntityLookupData<'T>

    member inline this.Iterate<'T when 'T :> Component> (f) : unit =
        let mutable data = Unchecked.defaultof<IEntityLookupData>
        if this.Lookup.TryGetValue (typeof<'T>, &data) then
            let data = data :?> EntityLookupData<'T>

            let inline iter i =
                let entity = data.Entities.Buffer.[i]

                if this.ActiveIndices.[entity.Index] then
                    f entity data.Components.Buffer.[i]

            for i = 0 to data.Entities.Count - 1 do iter i

    member inline this.Iterate<'T1, 'T2 when 'T1 :> Component and 'T2 :> Component> (f) : unit =
        let mutable data1 = Unchecked.defaultof<IEntityLookupData>
        let mutable data2 = Unchecked.defaultof<IEntityLookupData>
        if this.Lookup.TryGetValue (typeof<'T1>, &data1) && this.Lookup.TryGetValue (typeof<'T2>, &data2) then
            let data = [|data1;data2|] |> Array.minBy (fun x -> x.Entities.Count)
            let data1 = data1 :?> EntityLookupData<'T1>
            let data2 = data2 :?> EntityLookupData<'T2>

            let inline iter i =
                let entity = data.Entities.Buffer.[i]
    
                if this.ActiveIndices.[entity.Index] then
                    let comp1Index = data1.IndexLookup.[entity.Index]
                    let comp2Index = data2.IndexLookup.[entity.Index]
    
                    if comp1Index >= 0 && comp2Index >= 0 then
                        f entity data1.Components.Buffer.[comp1Index] data2.Components.Buffer.[comp2Index]
    
            for i = 0 to data.Entities.Count - 1 do iter i

    member inline this.Iterate<'T1, 'T2, 'T3 when 'T1 :> Component and 'T2 :> Component and 'T3 :> Component> (f) : unit =
        let mutable data1 = Unchecked.defaultof<IEntityLookupData>
        let mutable data2 = Unchecked.defaultof<IEntityLookupData>
        let mutable data3 = Unchecked.defaultof<IEntityLookupData>
        if this.Lookup.TryGetValue (typeof<'T1>, &data1) && this.Lookup.TryGetValue (typeof<'T2>, &data2) && 
           this.Lookup.TryGetValue (typeof<'T3>, &data3) then
            let data = [|data1;data2;data3|] |> Array.minBy (fun x -> x.Entities.Count)
            let data1 = data1 :?> EntityLookupData<'T1>
            let data2 = data2 :?> EntityLookupData<'T2>
            let data3 = data3 :?> EntityLookupData<'T3>
<commit_msg>Improved iteration time<commit_after>﻿namespace Foom.Ecs

open System
open System.Diagnostics
open System.Reflection
open System.Collections.Generic
open System.Collections.Concurrent
open System.Threading.Tasks
open System.Runtime.InteropServices

open Foom.Collections

#nowarn "9"

type IEntityLookupData =

    abstract Entities : Entity UnsafeResizeArray with get

    abstract GetIndex : int -> int

    abstract GetComponent : int -> Component

open Events

[<ReferenceEquality>]
type EntityLookupData<'T when 'T :> Component> =
    {
        ComponentRemovedEvent: Event<ComponentRemoved<'T>>

        ComponentAddedTriggers : ResizeArray<'T -> unit>

        RemoveComponent: Entity -> unit

        IndexLookup: int []
        Entities: Entity UnsafeResizeArray
        Components: 'T UnsafeResizeArray
    }

    interface IEntityLookupData with

        member this.Entities = this.Entities

        member this.GetIndex id = this.IndexLookup.[id]

        member this.GetComponent index = this.Components.Buffer.[index] :> Component

type EntityBuilder = EntityBuilder of (Entity -> EntityManager -> unit)

and [<ReferenceEquality>] EntityManager =
    {
        EventAggregator: EventAggregator

        MaxEntityAmount: int
        Lookup: ConcurrentDictionary<Type, IEntityLookupData>

        ActiveVersions: uint32 []
        ActiveIndices: bool []

        mutable nextEntityIndex: int
        RemovedEntityQueue: Queue<Entity>

        EntityRemovals: ((Entity -> unit) ResizeArray) []

        EntitySpawnedEvent: Event<EntitySpawned>
        EntityDestroyedEvent: Event<EntityDestroyed>

        mutable CurrentIterations: int
        PendingQueue: Queue<unit -> unit>
    }

    static member Create (eventManager: EventAggregator, maxEntityAmount) =
        if maxEntityAmount <= 0 then
            failwith "Max entity amount must be greater than 0."

        let maxEntityAmount = maxEntityAmount + 1
        let lookup = ConcurrentDictionary<Type, IEntityLookupData> ()

        let activeVersions = Array.init maxEntityAmount (fun _ -> 0u)
        let activeIndices = Array.zeroCreate<bool> maxEntityAmount

        let mutable nextEntityIndex = 1
        let removedEntityQueue = Queue<Entity> () 

        let entityRemovals : ((Entity -> unit) ResizeArray) [] = Array.init maxEntityAmount (fun _ -> ResizeArray ())

        let entitySpawnedEvent = eventManager.GetEvent<EntitySpawned> ()
        let entityDestroyedEvent = eventManager.GetEvent<EntityDestroyed> ()

        {
            EventAggregator = eventManager
            MaxEntityAmount = maxEntityAmount
            Lookup = lookup
            ActiveVersions = activeVersions
            ActiveIndices = activeIndices
            nextEntityIndex = nextEntityIndex
            RemovedEntityQueue = removedEntityQueue
            EntityRemovals = entityRemovals
            EntitySpawnedEvent = entitySpawnedEvent
            EntityDestroyedEvent = entityDestroyedEvent
            CurrentIterations = 0
            PendingQueue = Queue ()
        }

    member inline this.IsValidEntity (entity: Entity) =
        not (entity.Index.Equals 0u) && this.ActiveVersions.[entity.Index].Equals entity.Version

    member inline this.ResolvePendingQueues () =
        if this.CurrentIterations = 0 then

            while this.PendingQueue.Count > 0 do
                let action = this.PendingQueue.Dequeue ()
                action ()

    member this.GetEntityLookupData<'T when 'T :> Component> () : EntityLookupData<'T> =
        let t = typeof<'T>
        let mutable data = Unchecked.defaultof<IEntityLookupData>
        match this.Lookup.TryGetValue(t, &data) with
        | true -> data :?> EntityLookupData<'T>
        | _ ->
            let triggers = ResizeArray ()

            let added = this.EventAggregator.GetComponentAddedEvent<'T> ()
            triggers.Add (fun o -> added.Trigger o)

            if t.GetTypeInfo().BaseType <> typeof<Component> then
                let t = t.GetTypeInfo().BaseType
                let mutable trigger = Unchecked.defaultof<obj -> unit>
                if this.EventAggregator.TryGetComponentAddedTrigger (t, &trigger) then
                    triggers.Add (fun o -> trigger o)

                if t.GetTypeInfo().BaseType <> typeof<Component> then
                    let t = t.GetTypeInfo().BaseType
                    let mutable trigger = Unchecked.defaultof<obj -> unit>
                    if this.EventAggregator.TryGetComponentAddedTrigger (t, &trigger) then
                        triggers.Add (fun o -> trigger o)

            let factory t =
                let data =
                    {
                        ComponentRemovedEvent = this.EventAggregator.GetEvent<ComponentRemoved<'T>> ()

                        ComponentAddedTriggers = triggers

                        RemoveComponent = fun entity -> this.Remove<'T> entity

                        IndexLookup = Array.init this.MaxEntityAmount (fun _ -> -1) // -1 means that no component exists for that entity
                        Entities = UnsafeResizeArray.Create this.MaxEntityAmount
                        Components = UnsafeResizeArray.Create this.MaxEntityAmount
                    }

                data :> IEntityLookupData

            this.Lookup.GetOrAdd(t, factory) :?> EntityLookupData<'T>

    member inline this.Iterate<'T when 'T :> Component> (f) : unit =
        let mutable data = Unchecked.defaultof<IEntityLookupData>
        if this.Lookup.TryGetValue (typeof<'T>, &data) then
            let data = data :?> EntityLookupData<'T>

            let entities = data.Entities.Buffer
            let activeIndices = this.ActiveIndices
            let components = data.Components.Buffer

            let inline iter i =
                let entity = entities.[i]

                if activeIndices.[entity.Index] then
                    f entity components.[i]

            for i = 0 to data.Entities.Count - 1 do iter i

    member inline this.Iterate<'T1, 'T2 when 'T1 :> Component and 'T2 :> Component> (f) : unit =
        let mutable data1 = Unchecked.defaultof<IEntityLookupData>
        let mutable data2 = Unchecked.defaultof<IEntityLookupData>
        if this.Lookup.TryGetValue (typeof<'T1>, &data1) && this.Lookup.TryGetValue (typeof<'T2>, &data2) then
            let data = [|data1;data2|] |> Array.minBy (fun x -> x.Entities.Count)
            let data1 = data1 :?> EntityLookupData<'T1>
            let data2 = data2 :?> EntityLookupData<'T2>

            let entities = data.Entities.Buffer
            let activeIndices = this.ActiveIndices
            let components1 = data1.Components.Buffer
            let components2 = data2.Components.Buffer
            let lookup1 = data1.IndexLookup
            let lookup2 = data2.IndexLookup

            let inline iter i =
                let entity = entities.[i]
    
                if activeIndices.[entity.Index] then
                    let comp1Index = lookup1.[entity.Index]
                    let comp2Index = lookup2.[entity.Index]
    
                    if comp1Index >= 0 && comp2Index >= 0 then
                        f entity components1.[comp1Index] components2.[comp2Index]
    
            for i = 0 to data.Entities.Count - 1 do iter i

    member inline this.Iterate<'T1, 'T2, 'T3 when 'T1 :> Component and 'T2 :> Component and 'T3 :> Component> (f) : unit =
        let mutable data1 = Unchecked.defaultof<IEntityLookupData>
        let mutable data2 = Unchecked.defaultof<IEntityLookupData>
        let mutable data3 = Unchecked.defaultof<IEntityLookupData>
        if this.Lookup.TryGetValue (typeof<'T1>, &data1) && this.Lookup.TryGetValue (typeof<'T2>, &data2) && 
           this.Lookup.TryGetValue (typeof<'T3>, &data3) then
            let data = [|data1;data2;data3|] |> Array.minBy (fun x -> x.Entities.Count)
            let data1 = data1 :?> EntityLookupData<'T1>
            let data2 = data2 :?> EntityLookupData<'T2>
            let data3 = data3 :?> EntityLookupData<'T3>

{'fraction_non_alphanumeric': 0.087686805466854, 'fraction_numerical': 0.010090688465959893, 'mean_word_length': 2.2293256341513716, 'pattern_counts': {'":': 0, '<': 111, '<?xml version=': 0, '>': 199, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3283929', 'n_tokens_mistral': 4249, 'n_tokens_neox': 4185, 'n_words': 1061}