コード例 #1
0
        public override void PatchContents(IReadOnlyList <MiscContent> contents)
        {
            foreach (var content in contents)
            {
                var typeName = $"{content.Namespace}.{content.TypeName}";

                var method = ModModule.Find(typeName, false)?.FindMethod(content.Method);
                if (method?.HasBody != true)
                {
                    continue;
                }

                var literalEmitter = new LiteralEmitter(method, Provider);

                var instructions = method.Body.Instructions;
                for (int index = 0, listIndex = 0; index < instructions.Count && listIndex < content.Contents.Count; index++)
                {
                    var instruction = instructions[index];
                    if (instruction.OpCode != OpCodes.Call || !(instruction.Operand is IMethodDefOrRef m) ||
                        !string.Equals(m.Name, "NewText", StringComparison.Ordinal))
                    {
                        continue;
                    }

                    // `Main.NewText` parameters
                    instruction = instructions[index - 5];

                    if (instruction.OpCode.Equals(OpCodes.Ldstr))
                    {
                        literalEmitter.Emit(instruction, content.Contents[listIndex++]);
                    }
                    else if (instruction.OpCode.Equals(OpCodes.Call) &&
                             instruction.Operand is MemberRef n &&
                             string.Equals(n.Name, nameof(string.Concat), StringComparison.Ordinal))
                    {
                        var list = method.Body.FindStringLiteralsOf(instruction);

                        // the list given above is in reverse order
                        foreach (var ldstr in list.Reverse())
                        {
                            literalEmitter.Emit(ldstr, content.Contents[listIndex++]);

                            if (listIndex >= content.Contents.Count)
                            {
                                break;
                            }
                        }
                    }
                }
            }
        }
コード例 #2
0
        static void Main(string[] args)
        {
            Aardvark.Base.IL.TypeBuilderTest.run();
            Environment.Exit(0);

            #region Basic Mod usage

            // Create a modref cell. can be changed via side effects
            var input = Mod.Init(10);

            var output = input.Map(x => x * 2);

            Console.WriteLine($"output was: {output}");
            // Prints: output was Aardvark.Base.Incremental.ModModule+MapMod`2[System.Int32,System.Int32]
            // not what we expected. Since mods are lazy and tostring does not force them we need
            // to pull the value out of it.

            Console.WriteLine($"output was: {output.GetValue()}"); // F# equivalent: Mod.force : IMod<'a> -> 'a
            // output was: 20

            using (Adaptive.Transaction)
            {
                input.Value = 20;
            }

            Console.WriteLine($"output was: {output.GetValue()}");
            // output was: 40

            // semantically, output now dependens on input declaratively.
            // the dependency graph looks like:
            //
            //       (x) => x * 2
            // input ----------------> output
            // mods are nodes, and the edges are annotated with transition functions.

            // Users of Rx might see the pattern. outputs is an observer, while input is observable.
            // so the systems are equivalent ?! but mod must be better - otherwise this tutorial is useless right?

            #endregion

            #region Obserable semantics

            // A modref could be an observable which never ends and has an initial value.
            var inputObs = new Subject <int>();
            inputObs.OnNext(10);
            var outputObs = inputObs.Select(x => x * 2);

            var globalStore         = 0;
            var sideEffectToObserve = outputObs.Subscribe(x => globalStore = x);

            Console.WriteLine($"outputObs was: {globalStore}");
            // outputObs was: 0
            // unexpected? no. this is due to subscription semantics.
            // Note: ReplaySubject instead of subject has right semantics although i have doubts about memory leaks.

            inputObs.OnNext(10);
            Console.WriteLine($"outputObs was: {globalStore}");
            // outputObs was: 20

            inputObs.OnNext(20);
            Console.WriteLine($"outputObs was: {globalStore}");
            // outputObs was: 40


            // what happens if we have data flow graphs with sinks (2 ingoing edges conceptually)

            var inputA = new Subject <int>();
            var inputB = new Subject <int>();

            var reexCount = 0;

            inputA.Merge(inputB).Subscribe(x =>
            {
                reexCount++;
                Console.WriteLine($"a+b was: {x}");
            });

            inputA.OnNext(1);
            inputB.OnNext(2);
            Console.WriteLine($"reexCount was: {reexCount}");
            // reexCount was: 2

            // did you expect 2? of course. this is the semantics of merge.

            // what iff we only want to have a batch change, say, change 2 inputs simultationusly?
            // then we need different semantics.
            var inputA3 = new Subject <int>();
            var inputB3 = new Subject <int>();

            reexCount = 0;
            inputA3.SelectMany(a =>
                               inputB3.Select(b =>
            {
                reexCount++;
                return(a + b);
            }
                                              )
                               ).Subscribe(r =>
                                           Console.WriteLine($"result was: {r} reexCount was: {reexCount}")
                                           );

            inputA3.OnNext(1);
            inputB3.OnNext(2);
            // result was: 3 reexCount was: 1

            // If we switch to replay subject we get 2 again...
            var inputA2 = new ReplaySubject <int>();
            var inputB2 = new ReplaySubject <int>();

            reexCount = 0;
            inputA2.SelectMany(a =>
                               inputB2.Select(b =>
            {
                reexCount++;
                return(a + b);
            }
                                              )
                               ).Subscribe(r =>
                                           Console.WriteLine($"result was: {r} reexCount was: {reexCount}")
                                           );

            inputA2.OnNext(1);
            inputB2.OnNext(2);
            // result was: 3 reexCount was: 2

            // Let us see how this looks like in Mod:
            reexCount = 0;
            var inputAM = Mod.Init(1);
            var inputBM = Mod.Init(2);
            var aPlusB  = inputAM.Map(inputBM,
                                      (a, b) => {
                reexCount++;
                return(a + b);
            });

            Console.WriteLine($"mod,a+b was: {aPlusB.GetValue()}, reexCount: {reexCount}");
            // mod,a+b was: 3, reexCount: 1

            // special note: Select2 was not defined at the time or writing of this tutorial, but
            // it is available in F#. How could we access the F# verion?
            var aPlusB2 = ModModule.map2(FSharpFuncUtil.Create <int, int, int>((a, b) => a + b), inputAM, inputBM);
            // steps required:
            // (1) F# map2 is defined in Mod module. usage Mod.map2 (+) a b
            // so we need this map module. by convention, modules with colliding type names
            // are exported with the module suffix. so the function lives in ModModule.
            // (2) our f# function wants a f# function and not an instance of type System.Func. use a conversion
            // (3) C# has no real type inference, so most of the time you'll need to annotate stuff.
            // that is - we just used a f# function with no c# friendly interface in c#.
            Console.WriteLine($"mod,a+b was: {aPlusB2.GetValue()}, reexCount: {reexCount}");

            reexCount = 0;
            using (Adaptive.Transaction)
            {
                inputAM.Value = 20;
                inputBM.Value = 30;
            }

            Console.WriteLine($"mod,a+b was: {aPlusB.GetValue()}, reexCount: {reexCount}");
            // mod,a+b was: 50, reexCount: 1

            // so we have batch changes in the mod system. but this is cheating, right?
            // because we used an optimized combinator which does this, right?
            // we can do a low level implementation instead.

            var aPlusBBind = inputAM.Bind(a => inputBM.Map(b =>
            {
                reexCount++;
                return(a + b);
            }));

            reexCount = 0;
            using (Adaptive.Transaction)
            {
                inputAM.Value = 20;
                inputBM.Value = 30;
            }

            Console.WriteLine($"modbind,a+b was: {aPlusBBind.GetValue()}, reexCount: {reexCount}");
            // modbind,a+b was: 50, reexCount: 1
            // interesting. also here we have tight reexecution count.

            reexCount = 0;
            using (Adaptive.Transaction)
            {
                inputAM.Value = 20;
                inputBM.Value = 30;
            }

            Console.WriteLine($"modbind2,a+b was: {aPlusBBind.GetValue()}, reexCount: {reexCount}");
            // modbind2,a + b was: 50, reexCount:0
            // aha - we have reexCount=0 because the change was a pseudo change (values changed to old values)

            /*
             * So what is the result of this analysis? Rx has precise semantics. You get what you want. But
             * you need to know how you want it and there are many solutions.
             * So Mod is the same as observable, but can do less because we do not have precise control about
             * reexecution semantics (although semantics seems to be nice, right)?
             */

            // One could use the mod system as strange implementation of observable of course.
            var inputEvil  = Mod.Init(10);
            var outputEvil = Mod.Init(0);
            var sub        = inputEvil.UnsafeRegisterCallbackNoGcRoot(i =>
            {
                using (Adaptive.Transaction)
                {
                    outputEvil.Value = i * 2;
                }
            });

            using (Adaptive.Transaction)
            {
                inputEvil.Value = 20;
            }
            Console.WriteLine($"evilCallback: {outputEvil.GetValue()}");
            // evilCallback: 40

            /* so this works. but this is evil.
             * /* this code has no dependency graph. all is modelled via side effects.
             * /* The following list sponsored by gh gives some reasons against callbacks:
             * 1) The Mod-system is capable of handling those callbacks but their cost is massive compared to Rx
             * 2) Callbacks tend to keep their closure alive(causing memory leaks)
             * 3) Callbacks are hard to debug
             * 4) Eager evaluation wastes time (by design and especially when using the mod-system)
             * 5) Concurrency is not controlled in any way
             * 6) Callers of transact suddenly block until their callbacks finish(deadlock scenarios etc.)
             * 7) Rx was already invented (so why exploit our system to simulate it) */

            #endregion

            /*
             * So finally, the answer is: NO
             * Rx and Mod is something completly different.
             * - Mod is lazy / Obs pushes values to their subscribers immediately
             * - Mods have batch changes intrinsic to the system / in Obs semantics depends on combinators
             * - Mods build dependency graphs and try to reduce recomputation overhead / Obs don't care about algorithmic complexity, Obs cannot be used to implement adaptive data structures
             * - Mods have on goal: make the outputs consistent iff they are demaned. / Obs populates all values according to the combinators used (e.g. merge)
             */

            // As a result there are many things which do not fit to observables, others do not fit to mods.

            // Given this list, Mods are particularly not immediately usable for tracking changes manually.
            var resendOverNetwork = Mod.Init(false);
            inputEvil.UnsafeRegisterCallbackNoGcRoot(s =>
            {
                using (Adaptive.Transaction)
                {
                    resendOverNetwork.Value = true;
                }
            }
                                                     );
            resendOverNetwork.UnsafeRegisterCallbackNoGcRoot(_ =>
            {
                inputEvil.GetValue().ToString(); // send new contents of inputEvil
            });
            // UnsafeRegisterCallbackNoGcRoot is a hack in the system to allow unproblematic side effects
            // to be coupled with reecution. The exeuction of order of callbacks, especially callbacks
            // which run callbacks via transactions is highly unspecified. In fact
            // UnsafeRegisterCallbackNoGcRoot has no defined semantics.

            // One little note: (in the current implementation) all callbacks are executed eventuallly, but maybe to often.
            // This is very comparable to LINQ mixed with side effects. LINQ has lazy evaluation and using
            // side effects inside is just nonesense. Fortunately LINQ has no public cheat API --- one cannot
            // simply "side-effect" elements into an existing enumerable sequence
            // (ok we can but peoply luckily rarely mix side effects with linq because meijer said it is evil [1]).
            // [1] https://www.youtube.com/watch?v=UuamC0T3hv8
            // Of course we could restrict the mod API (by removing callbacks), but
            // we still wanted ways to do unsafe stuff (in less than 0.01% of the code).
            // At this point we shall mention that the complete rendering backend works without callbacks,
            // but rendering things could be as well considered as rather imperative problem.
            // If you really want to attach callbacks to some mod, maybe either:
            // (1) the problem does not fit the declarative incremental computation modlel
            // (2) the problem fits, but some parts of the current solution are not delcarative (either because of (1), because of hacks or other non-declarative parts)

            /* You know might think: "Why all this complexity. Why not just use Observables where
             * appropriate and ad hoc techniques when they not work as nice as they should. I pretty
             * lived quite good the last years without a mod system and this stuff".
             * Cases in which the mod system is a good fit can greatly benefit from the usage of mods.
             * The changes are not local, but having a mod system at hand completely changes the way
             * on can structure programs. In fact, i think programming with mods is a completely different
             * paradigm. So Take your time and help us making the libraries better.
             */
        }